@tailorkit/react 0.1.0-beta.3 → 0.1.0-beta.5
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/components/context.d.ts +0 -1
- package/dist/components/index.d.ts +0 -1
- package/dist/components/render.d.ts +0 -1
- package/dist/components/root.d.ts +0 -1
- package/dist/hooks/use-apps.d.ts +0 -1
- package/dist/hooks/use-current-screen.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/node-store.d.ts +0 -1
- package/dist/primitives.d.ts +0 -1
- package/dist/remote-context.d.ts +0 -1
- package/dist/remote-view.d.ts +0 -1
- package/dist/tailor-kit.d.ts +0 -1
- package/package.json +4 -15
- package/dist/components/context.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/render.d.ts.map +0 -1
- package/dist/components/root.d.ts.map +0 -1
- package/dist/hooks/use-apps.d.ts.map +0 -1
- package/dist/hooks/use-current-screen.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/node-store.d.ts.map +0 -1
- package/dist/primitives.d.ts.map +0 -1
- package/dist/remote-context.d.ts.map +0 -1
- package/dist/remote-view.d.ts.map +0 -1
- package/dist/tailor-kit.d.ts.map +0 -1
- package/src/__tests__/node-store.test.ts +0 -206
- package/src/__tests__/primitives.test.tsx +0 -85
- package/src/__tests__/tailor-kit.test.tsx +0 -424
- package/src/__tests__/tailor-kit.types.tsx +0 -151
- package/src/components/context.ts +0 -21
- package/src/components/index.ts +0 -1
- package/src/components/render.tsx +0 -116
- package/src/components/root.tsx +0 -37
- package/src/hooks/use-apps.ts +0 -47
- package/src/hooks/use-current-screen.ts +0 -81
- package/src/index.ts +0 -8
- package/src/node-store.ts +0 -131
- package/src/primitives.tsx +0 -261
- package/src/remote-context.ts +0 -11
- package/src/remote-view.tsx +0 -232
- package/src/tailor-kit.tsx +0 -501
package/src/remote-view.tsx
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
Fragment,
|
|
4
|
-
createElement,
|
|
5
|
-
memo,
|
|
6
|
-
useCallback,
|
|
7
|
-
useContext,
|
|
8
|
-
useEffect,
|
|
9
|
-
useMemo,
|
|
10
|
-
useRef,
|
|
11
|
-
useState,
|
|
12
|
-
useSyncExternalStore,
|
|
13
|
-
} from "react";
|
|
14
|
-
import type { ReactNode } from "react";
|
|
15
|
-
import { createIframeUiHost } from "@tailorkit/sandbox/host";
|
|
16
|
-
import type { HostToWorkerPayload, RemoteElementNode } from "@tailorkit/sandbox/protocol";
|
|
17
|
-
import { NodeStore } from "./node-store";
|
|
18
|
-
import { RemoteUIContext } from "./remote-context";
|
|
19
|
-
import type { RemoteViewContext } from "./remote-context";
|
|
20
|
-
|
|
21
|
-
const formatError = (error: Error): string => error.message;
|
|
22
|
-
|
|
23
|
-
interface RemoteErrorBoundaryProps {
|
|
24
|
-
children?: ReactNode;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface RemoteErrorBoundaryState {
|
|
28
|
-
error: Error | null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
class RemoteErrorBoundary extends Component<RemoteErrorBoundaryProps, RemoteErrorBoundaryState> {
|
|
32
|
-
state: RemoteErrorBoundaryState = { error: null };
|
|
33
|
-
|
|
34
|
-
static getDerivedStateFromError(error: Error): RemoteErrorBoundaryState {
|
|
35
|
-
return { error };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static componentDidCatch(error: Error): void {
|
|
39
|
-
console.error("TailorKit remote app failed", error);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
render(): ReactNode {
|
|
43
|
-
if (this.state.error) {
|
|
44
|
-
return createElement("div", null, formatError(this.state.error));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return this.props.children;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface RemoteViewHostProps {
|
|
52
|
-
appUrl: string | URL;
|
|
53
|
-
components: Record<string, unknown>;
|
|
54
|
-
createIframe?: () => HTMLIFrameElement;
|
|
55
|
-
props?: Record<string, unknown>;
|
|
56
|
-
runtimeUrl?: string | URL;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function RemoteViewHost({
|
|
60
|
-
appUrl,
|
|
61
|
-
components,
|
|
62
|
-
createIframe,
|
|
63
|
-
props,
|
|
64
|
-
runtimeUrl,
|
|
65
|
-
}: RemoteViewHostProps): ReactNode {
|
|
66
|
-
const appKey = appUrl.toString();
|
|
67
|
-
const storeRef = useRef<NodeStore | null>(null);
|
|
68
|
-
if (storeRef.current === null) {
|
|
69
|
-
storeRef.current = new NodeStore();
|
|
70
|
-
}
|
|
71
|
-
const store = storeRef.current;
|
|
72
|
-
const appKeyRef = useRef(appKey);
|
|
73
|
-
if (appKeyRef.current !== appKey) {
|
|
74
|
-
appKeyRef.current = appKey;
|
|
75
|
-
store.clear();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const dispatchRef = useRef<((payload: HostToWorkerPayload) => void) | null>(null);
|
|
79
|
-
const [error, setError] = useState<Error | null>(null);
|
|
80
|
-
const [status, setStatus] = useState<"error" | "ready" | "starting">("starting");
|
|
81
|
-
|
|
82
|
-
const ctxRef = useRef<RemoteViewContext>({
|
|
83
|
-
components,
|
|
84
|
-
dispatch: (payload) => dispatchRef.current?.(payload),
|
|
85
|
-
store,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
ctxRef.current.components = components;
|
|
90
|
-
}, [components]);
|
|
91
|
-
|
|
92
|
-
const ui = useMemo(() => {
|
|
93
|
-
const ctx = ctxRef.current;
|
|
94
|
-
return createElement(
|
|
95
|
-
RemoteUIContext.Provider,
|
|
96
|
-
{ value: ctx },
|
|
97
|
-
createElement(
|
|
98
|
-
RemoteErrorBoundary,
|
|
99
|
-
{ key: appKey },
|
|
100
|
-
createElement(RemoteRoot, { store: ctx.store }),
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
}, [appKey]);
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
const host = createIframeUiHost(appUrl, {
|
|
107
|
-
createIframe,
|
|
108
|
-
onError: (error) => {
|
|
109
|
-
console.error("TailorKit remote app failed", error);
|
|
110
|
-
setError(error);
|
|
111
|
-
setStatus("error");
|
|
112
|
-
},
|
|
113
|
-
props,
|
|
114
|
-
runtimeUrl,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
dispatchRef.current = (payload) => host.dispatch(payload);
|
|
118
|
-
|
|
119
|
-
const unsubscribe = host.subscribe(() => {
|
|
120
|
-
const tree = host.getSnapshot();
|
|
121
|
-
if (tree !== null) {
|
|
122
|
-
store.setSnapshot(tree);
|
|
123
|
-
setStatus("ready");
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
setStatus("starting");
|
|
128
|
-
setError(null);
|
|
129
|
-
host.mount();
|
|
130
|
-
|
|
131
|
-
return () => {
|
|
132
|
-
unsubscribe();
|
|
133
|
-
dispatchRef.current = null;
|
|
134
|
-
host.destroy();
|
|
135
|
-
};
|
|
136
|
-
}, [appUrl, createIframe, props, runtimeUrl, store]);
|
|
137
|
-
|
|
138
|
-
if (status === "error" && error) {
|
|
139
|
-
return createElement("div", null, formatError(error));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return ui;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
interface RemoteRootProps {
|
|
146
|
-
store: RemoteViewContext["store"];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function RemoteRoot({ store }: RemoteRootProps): ReactNode {
|
|
150
|
-
const subscribe = useCallback((listener: () => void) => store.subscribeRoot(listener), [store]);
|
|
151
|
-
const getSnapshot = useCallback(() => store.getRootId(), [store]);
|
|
152
|
-
|
|
153
|
-
const rootId = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
154
|
-
if (rootId === null) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return <RemoteView nodeId={rootId} />;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
interface RemoteElementViewProps {
|
|
162
|
-
ctx: RemoteViewContext;
|
|
163
|
-
node: RemoteElementNode;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const RemoteElementView = memo(({ ctx, node }: RemoteElementViewProps): ReactNode => {
|
|
167
|
-
const { dispatch, components } = ctx;
|
|
168
|
-
|
|
169
|
-
const children = node.children.map((child) => <RemoteView key={child.id} nodeId={child.id} />);
|
|
170
|
-
|
|
171
|
-
const component = components[node.type];
|
|
172
|
-
|
|
173
|
-
if (component === undefined) {
|
|
174
|
-
throw new Error(`TailorKit component "${node.type}" is not registered.`);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const props: Record<string, unknown> = { ...node.props };
|
|
178
|
-
|
|
179
|
-
for (const binding of node.callbacks ?? []) {
|
|
180
|
-
props[binding.callback] = (...args: unknown[]) => {
|
|
181
|
-
dispatch({
|
|
182
|
-
data: {
|
|
183
|
-
args: args.slice(0, binding.inputCount),
|
|
184
|
-
event: binding.event,
|
|
185
|
-
nodeId: node.id,
|
|
186
|
-
},
|
|
187
|
-
type: "dispatchCallback",
|
|
188
|
-
});
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
193
|
-
return createElement(component as any, props, ...children);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
interface RemoteViewProps {
|
|
197
|
-
nodeId: string;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export const RemoteView = memo(function RemoteView({ nodeId }: RemoteViewProps): ReactNode {
|
|
201
|
-
const ctx = useContext(RemoteUIContext);
|
|
202
|
-
if (ctx === null) {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const { store } = ctx;
|
|
207
|
-
|
|
208
|
-
const subscribe = useCallback(
|
|
209
|
-
(listener: () => void) => store.subscribe(nodeId, listener),
|
|
210
|
-
[store, nodeId],
|
|
211
|
-
);
|
|
212
|
-
const getSnapshot = useCallback(() => store.getNode(nodeId), [store, nodeId]);
|
|
213
|
-
|
|
214
|
-
const node = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
215
|
-
if (node === null) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (node.kind === "text") {
|
|
220
|
-
return node.text as unknown as ReactNode;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const childElements = node.children.map((child) => (
|
|
224
|
-
<RemoteView key={child.id} nodeId={child.id} />
|
|
225
|
-
));
|
|
226
|
-
|
|
227
|
-
if (node.kind === "fragment") {
|
|
228
|
-
return createElement(Fragment, { key: node.id }, ...childElements);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return <RemoteElementView node={node} ctx={ctx} />;
|
|
232
|
-
});
|