@tailorkit/react 0.1.0-beta.19 → 0.1.0-beta.21
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 +3 -0
- package/dist/components/context.d.ts +1 -1
- package/dist/components/slot.d.ts +20 -11
- package/dist/hooks/use-apps.d.ts +5 -4
- package/dist/hooks/use-slot-instances.d.ts +7 -7
- package/dist/index.js +195 -794
- package/dist/remote-context.d.ts +1 -1
- package/dist/scope-query.d.ts +1 -5
- package/dist/slot-view.d.ts +1 -22
- package/dist/tailorkit.d.ts +5 -36
- package/package.json +6 -5
- package/dist/node-store.d.ts +0 -16
- package/dist/preview-manager.d.ts +0 -11
- package/dist/store.d.ts +0 -43
- package/dist/view-registry.d.ts +0 -14
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/components/slot.tsx
|
|
2
|
-
import {
|
|
3
|
-
import { useCallback as useCallback3, useEffect as useEffect3, useId as useId2, useMemo as useMemo4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
2
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useId as useId2, useMemo as useMemo5, useRef as useRef3, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
4
3
|
|
|
5
4
|
// src/hooks/use-stable-context.ts
|
|
6
5
|
import { useRef } from "react";
|
|
@@ -14,32 +13,8 @@ function useStableContext(value) {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
// src/hooks/use-slot-instances.ts
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
// src/slot-view.ts
|
|
20
|
-
import { composeViewContext, getViewHierarchy } from "@tailorkit/core/views";
|
|
21
|
-
function selectSlotView(views, slot, activePath, schema) {
|
|
22
|
-
const supported = schema.slots[slot]?.views ?? [];
|
|
23
|
-
const path = getViewHierarchy(activePath).find(
|
|
24
|
-
(path2) => supported.includes(path2) && views.some((view2) => view2.slot === slot && view2.path === path2)
|
|
25
|
-
);
|
|
26
|
-
const view = views.find((view2) => view2.slot === slot && view2.path === path);
|
|
27
|
-
return !view || view.disabled ? null : view;
|
|
28
|
-
}
|
|
29
|
-
function resolveSlotView(views, slot, activeView, schema) {
|
|
30
|
-
const view = selectSlotView(views, slot, activeView.view, schema);
|
|
31
|
-
if (!view) return null;
|
|
32
|
-
return {
|
|
33
|
-
...composeViewContext(view.path, {
|
|
34
|
-
layers: activeView.layers,
|
|
35
|
-
declaredViews: Object.keys(schema.views)
|
|
36
|
-
}),
|
|
37
|
-
instances: Boolean(view.instances)
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/hooks/use-slot-instances.ts
|
|
42
|
-
import { useCallback, useEffect, useMemo, useRef as useRef2, useState, useSyncExternalStore } from "react";
|
|
16
|
+
import { createSlotInstancesStore } from "@tailorkit/client-core";
|
|
17
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
43
18
|
|
|
44
19
|
// src/components/context.ts
|
|
45
20
|
import { createContext, useContext } from "react";
|
|
@@ -57,114 +32,115 @@ function useTailorRootContext(component, expectedClient) {
|
|
|
57
32
|
return context;
|
|
58
33
|
}
|
|
59
34
|
|
|
35
|
+
// src/hooks/use-apps.ts
|
|
36
|
+
import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
37
|
+
import { matchesApp } from "@tailorkit/client-core";
|
|
38
|
+
|
|
39
|
+
// src/scope-query.ts
|
|
40
|
+
import { appendScopeSelection, normalizeScopeSelection } from "@tailorkit/client-core";
|
|
41
|
+
|
|
42
|
+
// src/hooks/use-apps.ts
|
|
43
|
+
import { matchesApp as matchesApp2 } from "@tailorkit/client-core";
|
|
44
|
+
function useApps(options = {}) {
|
|
45
|
+
return useAppsStore(useTailorRootContext("useApps").store, options);
|
|
46
|
+
}
|
|
47
|
+
function useAppsStore(store, options = {}) {
|
|
48
|
+
const snapshot = useAppsSnapshot(store, options);
|
|
49
|
+
const scopes = normalizeScopeSelection(options.scopes);
|
|
50
|
+
const appIds = normalizeScopeSelection(options.appIds);
|
|
51
|
+
const data = useMemo(
|
|
52
|
+
() => snapshot.status === "ready" ? snapshot.apps.filter((app) => matchesApp(app, scopes.scopes, appIds.scopes)) : void 0,
|
|
53
|
+
[snapshot, scopes.key, appIds.key]
|
|
54
|
+
);
|
|
55
|
+
const refetch = useCallback(
|
|
56
|
+
() => store.fetchApps({ force: true, staleTime: options.staleTime }),
|
|
57
|
+
[store, options.staleTime]
|
|
58
|
+
);
|
|
59
|
+
return { ...toUseAppsResult(snapshot, refetch), data };
|
|
60
|
+
}
|
|
61
|
+
function useAppsSnapshot(store, options = {}) {
|
|
62
|
+
const snapshot = useSyncExternalStore(
|
|
63
|
+
store.subscribeApps,
|
|
64
|
+
store.getAppsSnapshot,
|
|
65
|
+
store.getAppsSnapshot
|
|
66
|
+
);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
void store.fetchApps(options);
|
|
69
|
+
}, [store, options.staleTime]);
|
|
70
|
+
return snapshot;
|
|
71
|
+
}
|
|
72
|
+
function toUseAppsResult(snapshot, refetch) {
|
|
73
|
+
return {
|
|
74
|
+
data: snapshot.status === "ready" ? snapshot.apps : void 0,
|
|
75
|
+
error: snapshot.error,
|
|
76
|
+
isFetching: snapshot.isFetching,
|
|
77
|
+
isError: snapshot.status === "error",
|
|
78
|
+
isLoading: snapshot.status === "loading",
|
|
79
|
+
isPending: snapshot.status === "idle" || snapshot.status === "loading",
|
|
80
|
+
isSuccess: snapshot.status === "ready",
|
|
81
|
+
refetch,
|
|
82
|
+
status: snapshot.status
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
60
86
|
// src/hooks/use-slot-instances.ts
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
function useSlotInstances({
|
|
88
|
+
slot,
|
|
89
|
+
staleTime,
|
|
90
|
+
gcTime
|
|
91
|
+
}) {
|
|
92
|
+
const apps = useApps();
|
|
93
|
+
const instances = useResolvedSlotInstances(slot, apps.data, apps.status, apps.error, {
|
|
94
|
+
staleTime,
|
|
95
|
+
gcTime
|
|
96
|
+
});
|
|
97
|
+
const refetch = useCallback2(async () => {
|
|
98
|
+
if (apps.isSuccess) await instances.refetch();
|
|
99
|
+
else await apps.refetch();
|
|
100
|
+
}, [apps.isSuccess, apps.refetch, instances.refetch]);
|
|
101
|
+
return { ...instances, isFetching: apps.isFetching || instances.isFetching, refetch };
|
|
102
|
+
}
|
|
103
|
+
function useAppSlotInstances(app, slot) {
|
|
104
|
+
return useResolvedSlotInstances(slot, [app], "ready", null);
|
|
105
|
+
}
|
|
106
|
+
function useResolvedSlotInstances(slot, apps, appsStatus, appsError, { staleTime, gcTime } = {}) {
|
|
63
107
|
const { store } = useTailorRootContext("useSlotInstances");
|
|
64
|
-
const activeView =
|
|
108
|
+
const activeView = useSyncExternalStore2(
|
|
65
109
|
store.views.subscribe,
|
|
66
110
|
store.views.getSnapshot,
|
|
67
111
|
store.views.getSnapshot
|
|
68
112
|
);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
[store, app.id, app.currentDeployment?.id]
|
|
113
|
+
const options = useStableContext({ apps: apps ?? [], slot, activeView });
|
|
114
|
+
const query = useMemo2(
|
|
115
|
+
() => createSlotInstancesStore(store.client, {
|
|
116
|
+
...options,
|
|
117
|
+
appsStatus,
|
|
118
|
+
appsError,
|
|
119
|
+
staleTime,
|
|
120
|
+
gcTime
|
|
121
|
+
}),
|
|
122
|
+
[store.client, options, appsStatus, appsError, staleTime, gcTime]
|
|
80
123
|
);
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
const generation = useRef2(0);
|
|
88
|
-
const backend = useRef2(null);
|
|
89
|
-
const refetch = useCallback(async () => {
|
|
90
|
-
const id = ++generation.current;
|
|
91
|
-
backend.current?.close();
|
|
92
|
-
backend.current = null;
|
|
93
|
-
const publish = (status2, data, error = null) => {
|
|
94
|
-
if (id === generation.current) {
|
|
95
|
-
setSnapshot({
|
|
96
|
-
request,
|
|
97
|
-
status: status2,
|
|
98
|
-
data,
|
|
99
|
-
error
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
if (!request.activeView) {
|
|
104
|
-
publish("idle");
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (!request.views.some((view) => view.slot === slot && view.instances && !view.disabled)) {
|
|
108
|
-
publish("ready", []);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
publish("loading");
|
|
112
|
-
let client;
|
|
113
|
-
try {
|
|
114
|
-
await store.fetchMeta({ force: store.getMetaSnapshot().status === "error" });
|
|
115
|
-
if (id !== generation.current) return;
|
|
116
|
-
const meta = store.getMetaSnapshot();
|
|
117
|
-
if (meta.error) throw meta.error;
|
|
118
|
-
if (!meta.schema) throw new Error("TailorKit metadata is unavailable.");
|
|
119
|
-
const resolved = resolveSlotView(request.views, slot, request.activeView, meta.schema);
|
|
120
|
-
if (!resolved?.instances) {
|
|
121
|
-
publish("ready", []);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (resolved.status !== "ready") {
|
|
125
|
-
if (resolved.status === "error")
|
|
126
|
-
throw new Error(`Context for view "${resolved.view}" is unavailable.`);
|
|
127
|
-
publish("loading");
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
client = createClient({ getSession });
|
|
131
|
-
backend.current = client;
|
|
132
|
-
const instances = await client.action(resolveInstances, {
|
|
133
|
-
slot,
|
|
134
|
-
path: resolved.view,
|
|
135
|
-
context: resolved.context
|
|
136
|
-
});
|
|
137
|
-
publish("ready", instances);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
publish("error", void 0, error instanceof Error ? error : new Error(String(error)));
|
|
140
|
-
} finally {
|
|
141
|
-
client?.close();
|
|
142
|
-
if (backend.current === client) backend.current = null;
|
|
143
|
-
}
|
|
144
|
-
}, [request, store, slot, getSession]);
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
void refetch();
|
|
147
|
-
return () => {
|
|
148
|
-
generation.current += 1;
|
|
149
|
-
backend.current?.close();
|
|
150
|
-
backend.current = null;
|
|
151
|
-
};
|
|
152
|
-
}, [refetch]);
|
|
153
|
-
const status = snapshot.request === request ? snapshot.status : activeView ? "loading" : "idle";
|
|
124
|
+
const snapshot = useSyncExternalStore2(query.subscribe, query.getSnapshot, query.getSnapshot);
|
|
125
|
+
useEffect2(() => {
|
|
126
|
+
void query.fetch();
|
|
127
|
+
}, [query]);
|
|
128
|
+
const refetch = useCallback2(() => query.fetch({ force: true }), [query]);
|
|
154
129
|
return {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
status,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
isSuccess: status === "ready",
|
|
161
|
-
isError: status === "error",
|
|
130
|
+
...snapshot,
|
|
131
|
+
isError: snapshot.status === "error",
|
|
132
|
+
isPending: snapshot.status === "idle" || snapshot.status === "loading",
|
|
133
|
+
isLoading: snapshot.status === "loading",
|
|
134
|
+
isSuccess: snapshot.status === "ready",
|
|
162
135
|
refetch
|
|
163
136
|
};
|
|
164
137
|
}
|
|
165
138
|
|
|
139
|
+
// src/slot-view.ts
|
|
140
|
+
import { resolveSlotView, selectSlotView } from "@tailorkit/client-core";
|
|
141
|
+
|
|
166
142
|
// src/primitives.tsx
|
|
167
|
-
import { createContext as createContext2, useContext as useContext2, useId, useMemo as
|
|
143
|
+
import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo3 } from "react";
|
|
168
144
|
import { resolveTheme } from "@tailorkit/core/primitives/theme";
|
|
169
145
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
170
146
|
var PrimitiveThemeContext = createContext2(null);
|
|
@@ -331,7 +307,7 @@ var buildThemeCss = (viewId, theme) => {
|
|
|
331
307
|
};
|
|
332
308
|
var usePrimitiveNodeId = () => {
|
|
333
309
|
const id = useId();
|
|
334
|
-
return
|
|
310
|
+
return useMemo3(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
|
|
335
311
|
};
|
|
336
312
|
var Primitive = ({
|
|
337
313
|
children,
|
|
@@ -367,122 +343,16 @@ import {
|
|
|
367
343
|
Fragment,
|
|
368
344
|
createElement,
|
|
369
345
|
memo,
|
|
370
|
-
useCallback as
|
|
346
|
+
useCallback as useCallback3,
|
|
371
347
|
useContext as useContext3,
|
|
372
|
-
useEffect as
|
|
373
|
-
useMemo as
|
|
374
|
-
useRef as
|
|
375
|
-
useState
|
|
376
|
-
useSyncExternalStore as
|
|
348
|
+
useEffect as useEffect3,
|
|
349
|
+
useMemo as useMemo4,
|
|
350
|
+
useRef as useRef2,
|
|
351
|
+
useState,
|
|
352
|
+
useSyncExternalStore as useSyncExternalStore3
|
|
377
353
|
} from "react";
|
|
378
354
|
import { createIframeUiHost } from "@tailorkit/sandbox/host";
|
|
379
|
-
|
|
380
|
-
// src/node-store.ts
|
|
381
|
-
var childIds = (children) => children.map((c) => c.id).join(",");
|
|
382
|
-
var nodeSignature = (node) => {
|
|
383
|
-
if (node.kind === "text") {
|
|
384
|
-
return `text:${node.text}`;
|
|
385
|
-
}
|
|
386
|
-
if (node.kind === "fragment") {
|
|
387
|
-
return `frag:${childIds(node.children)}`;
|
|
388
|
-
}
|
|
389
|
-
const propsStr = JSON.stringify(node.props);
|
|
390
|
-
const callbacksStr = (node.callbacks ?? []).map((binding) => `${binding.callback}:${binding.event}`).join(",");
|
|
391
|
-
return `elem:${node.type}|${childIds(node.children)}|${propsStr}|${callbacksStr}`;
|
|
392
|
-
};
|
|
393
|
-
var flattenTree = (node, out) => {
|
|
394
|
-
out.set(node.id, node);
|
|
395
|
-
if (node.kind === "text") {
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
for (const child of node.children) {
|
|
399
|
-
flattenTree(child, out);
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
var NodeStore = class {
|
|
403
|
-
nodes = /* @__PURE__ */ new Map();
|
|
404
|
-
signatures = /* @__PURE__ */ new Map();
|
|
405
|
-
nodeListeners = /* @__PURE__ */ new Map();
|
|
406
|
-
rootListeners = /* @__PURE__ */ new Set();
|
|
407
|
-
rootId = null;
|
|
408
|
-
clear() {
|
|
409
|
-
const nodeIds = [...this.nodeListeners.keys()];
|
|
410
|
-
const hadRoot = this.rootId !== null;
|
|
411
|
-
this.nodes = /* @__PURE__ */ new Map();
|
|
412
|
-
this.signatures = /* @__PURE__ */ new Map();
|
|
413
|
-
this.rootId = null;
|
|
414
|
-
for (const id of nodeIds) {
|
|
415
|
-
const subs = this.nodeListeners.get(id);
|
|
416
|
-
if (subs) {
|
|
417
|
-
for (const l of subs) {
|
|
418
|
-
l();
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if (hadRoot) {
|
|
423
|
-
for (const l of this.rootListeners) {
|
|
424
|
-
l();
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
setSnapshot(tree) {
|
|
429
|
-
const next = /* @__PURE__ */ new Map();
|
|
430
|
-
flattenTree(tree, next);
|
|
431
|
-
const changed = /* @__PURE__ */ new Set();
|
|
432
|
-
for (const [id, node] of next) {
|
|
433
|
-
const sig = nodeSignature(node);
|
|
434
|
-
if (this.signatures.get(id) !== sig) {
|
|
435
|
-
changed.add(id);
|
|
436
|
-
this.signatures.set(id, sig);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
for (const id of this.signatures.keys()) {
|
|
440
|
-
if (!next.has(id)) {
|
|
441
|
-
this.signatures.delete(id);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
this.nodes = next;
|
|
445
|
-
const prevRootId = this.rootId;
|
|
446
|
-
this.rootId = tree.id;
|
|
447
|
-
for (const id of changed) {
|
|
448
|
-
const subs = this.nodeListeners.get(id);
|
|
449
|
-
if (subs) {
|
|
450
|
-
for (const l of subs) {
|
|
451
|
-
l();
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (prevRootId !== this.rootId) {
|
|
456
|
-
for (const l of this.rootListeners) {
|
|
457
|
-
l();
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
getRootId() {
|
|
462
|
-
return this.rootId;
|
|
463
|
-
}
|
|
464
|
-
getNode(id) {
|
|
465
|
-
return this.nodes.get(id) ?? null;
|
|
466
|
-
}
|
|
467
|
-
subscribe(id, listener) {
|
|
468
|
-
let subs = this.nodeListeners.get(id);
|
|
469
|
-
if (!subs) {
|
|
470
|
-
subs = /* @__PURE__ */ new Set();
|
|
471
|
-
this.nodeListeners.set(id, subs);
|
|
472
|
-
}
|
|
473
|
-
subs.add(listener);
|
|
474
|
-
return () => {
|
|
475
|
-
subs.delete(listener);
|
|
476
|
-
if (subs.size === 0) {
|
|
477
|
-
this.nodeListeners.delete(id);
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
subscribeRoot(listener) {
|
|
482
|
-
this.rootListeners.add(listener);
|
|
483
|
-
return () => this.rootListeners.delete(listener);
|
|
484
|
-
}
|
|
485
|
-
};
|
|
355
|
+
import { NodeStore } from "@tailorkit/client-core";
|
|
486
356
|
|
|
487
357
|
// src/remote-context.ts
|
|
488
358
|
import { createContext as createContext3 } from "react";
|
|
@@ -515,34 +385,34 @@ function RemoteViewHost({
|
|
|
515
385
|
props
|
|
516
386
|
}) {
|
|
517
387
|
const appKey = appUrl.toString();
|
|
518
|
-
const storeRef =
|
|
388
|
+
const storeRef = useRef2(null);
|
|
519
389
|
if (storeRef.current === null) {
|
|
520
390
|
storeRef.current = new NodeStore();
|
|
521
391
|
}
|
|
522
392
|
const store = storeRef.current;
|
|
523
|
-
const appKeyRef =
|
|
393
|
+
const appKeyRef = useRef2(appKey);
|
|
524
394
|
if (appKeyRef.current !== appKey) {
|
|
525
395
|
appKeyRef.current = appKey;
|
|
526
396
|
store.clear();
|
|
527
397
|
}
|
|
528
|
-
const hostRef =
|
|
529
|
-
const propsRef =
|
|
530
|
-
|
|
398
|
+
const hostRef = useRef2(null);
|
|
399
|
+
const propsRef = useRef2(props);
|
|
400
|
+
useEffect3(() => {
|
|
531
401
|
propsRef.current = props;
|
|
532
402
|
hostRef.current?.setProps(props);
|
|
533
403
|
}, [props]);
|
|
534
|
-
const dispatchRef =
|
|
535
|
-
const [error, setError] =
|
|
536
|
-
const [status, setStatus] =
|
|
537
|
-
const ctxRef =
|
|
404
|
+
const dispatchRef = useRef2(null);
|
|
405
|
+
const [error, setError] = useState(null);
|
|
406
|
+
const [status, setStatus] = useState("starting");
|
|
407
|
+
const ctxRef = useRef2({
|
|
538
408
|
components: components2,
|
|
539
409
|
dispatch: (payload) => dispatchRef.current?.(payload),
|
|
540
410
|
store
|
|
541
411
|
});
|
|
542
|
-
|
|
412
|
+
useEffect3(() => {
|
|
543
413
|
ctxRef.current.components = components2;
|
|
544
414
|
}, [components2]);
|
|
545
|
-
const ui =
|
|
415
|
+
const ui = useMemo4(() => {
|
|
546
416
|
const ctx = ctxRef.current;
|
|
547
417
|
return createElement(
|
|
548
418
|
RemoteUIContext.Provider,
|
|
@@ -554,7 +424,7 @@ function RemoteViewHost({
|
|
|
554
424
|
)
|
|
555
425
|
);
|
|
556
426
|
}, [appKey]);
|
|
557
|
-
|
|
427
|
+
useEffect3(() => {
|
|
558
428
|
const host = createIframeUiHost(appUrl, {
|
|
559
429
|
createIframe,
|
|
560
430
|
sourceText,
|
|
@@ -591,9 +461,9 @@ function RemoteViewHost({
|
|
|
591
461
|
return ui;
|
|
592
462
|
}
|
|
593
463
|
function RemoteRoot({ store }) {
|
|
594
|
-
const subscribe =
|
|
595
|
-
const getSnapshot =
|
|
596
|
-
const rootId =
|
|
464
|
+
const subscribe = useCallback3((listener) => store.subscribeRoot(listener), [store]);
|
|
465
|
+
const getSnapshot = useCallback3(() => store.getRootId(), [store]);
|
|
466
|
+
const rootId = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
597
467
|
if (rootId === null) {
|
|
598
468
|
return null;
|
|
599
469
|
}
|
|
@@ -627,12 +497,12 @@ function RemoteViewComponent({ nodeId }) {
|
|
|
627
497
|
return null;
|
|
628
498
|
}
|
|
629
499
|
const { store } = ctx;
|
|
630
|
-
const subscribe =
|
|
500
|
+
const subscribe = useCallback3(
|
|
631
501
|
(listener) => store.subscribe(nodeId, listener),
|
|
632
502
|
[store, nodeId]
|
|
633
503
|
);
|
|
634
|
-
const getSnapshot =
|
|
635
|
-
const node =
|
|
504
|
+
const getSnapshot = useCallback3(() => store.getNode(nodeId), [store, nodeId]);
|
|
505
|
+
const node = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
636
506
|
if (node === null) {
|
|
637
507
|
return null;
|
|
638
508
|
}
|
|
@@ -647,461 +517,12 @@ function RemoteViewComponent({ nodeId }) {
|
|
|
647
517
|
}
|
|
648
518
|
var RemoteView = memo(RemoteViewComponent);
|
|
649
519
|
|
|
650
|
-
// src/view-registry.ts
|
|
651
|
-
import { getViewDepth, isViewAncestor } from "@tailorkit/core/views";
|
|
652
|
-
function createViewRegistry() {
|
|
653
|
-
const entries = /* @__PURE__ */ new Map();
|
|
654
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
655
|
-
let snapshot = null;
|
|
656
|
-
let nextOrder = 0;
|
|
657
|
-
let scheduled = false;
|
|
658
|
-
const publish = () => {
|
|
659
|
-
if (scheduled) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
scheduled = true;
|
|
663
|
-
queueMicrotask(() => {
|
|
664
|
-
scheduled = false;
|
|
665
|
-
const ordered = [...entries.values()].toSorted(
|
|
666
|
-
(a, b) => getViewDepth(b.view) - getViewDepth(a.view) || b.order - a.order
|
|
667
|
-
);
|
|
668
|
-
const selected = ordered[0];
|
|
669
|
-
if (selected) {
|
|
670
|
-
const peers = ordered.filter(
|
|
671
|
-
(entry) => getViewDepth(entry.view) === getViewDepth(selected.view)
|
|
672
|
-
);
|
|
673
|
-
if (peers.length > 1) {
|
|
674
|
-
console.warn(
|
|
675
|
-
`TailorKit found multiple active views at the same hierarchy depth: ${peers.toReversed().map((entry) => `"${entry.view}"`).join(
|
|
676
|
-
", "
|
|
677
|
-
)}. TailorKit selected "${selected.view}" by mount order. Only one route at a hierarchy depth should call useRegisterView.`
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
snapshot = {
|
|
681
|
-
view: selected.view,
|
|
682
|
-
layers: ordered.filter((entry) => isViewAncestor(entry.view, selected.view)).toReversed().map((entry) => ({ path: entry.view, context: entry.context, status: entry.status }))
|
|
683
|
-
};
|
|
684
|
-
} else {
|
|
685
|
-
snapshot = null;
|
|
686
|
-
}
|
|
687
|
-
for (const listener of listeners) {
|
|
688
|
-
listener();
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
};
|
|
692
|
-
return {
|
|
693
|
-
getSnapshot: () => snapshot,
|
|
694
|
-
subscribe(listener) {
|
|
695
|
-
listeners.add(listener);
|
|
696
|
-
return () => {
|
|
697
|
-
listeners.delete(listener);
|
|
698
|
-
};
|
|
699
|
-
},
|
|
700
|
-
register(entry) {
|
|
701
|
-
entries.set(entry.id, { ...entry, order: entries.get(entry.id)?.order ?? nextOrder++ });
|
|
702
|
-
publish();
|
|
703
|
-
},
|
|
704
|
-
unregister(id) {
|
|
705
|
-
entries.delete(id);
|
|
706
|
-
publish();
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// src/preview-manager.ts
|
|
712
|
-
import {
|
|
713
|
-
createPreviewWebSocketClient,
|
|
714
|
-
previewMetadataSchema
|
|
715
|
-
} from "@tailorkit/client-platform/preview";
|
|
716
|
-
var empty = { revision: 0, source: null };
|
|
717
|
-
function viewerTokenExpiresAt(metadata) {
|
|
718
|
-
const expiresAt = Number(metadata.token.split(".", 1)[0]);
|
|
719
|
-
return Number.isSafeInteger(expiresAt) ? expiresAt : 0;
|
|
720
|
-
}
|
|
721
|
-
function bytesFromBase64(value) {
|
|
722
|
-
const raw = atob(value);
|
|
723
|
-
const bytes = new Uint8Array(raw.length);
|
|
724
|
-
for (let index = 0; index < raw.length; index++) {
|
|
725
|
-
bytes[index] = raw.codePointAt(index) ?? 0;
|
|
726
|
-
}
|
|
727
|
-
return bytes;
|
|
728
|
-
}
|
|
729
|
-
async function verify(candidate) {
|
|
730
|
-
let source = null;
|
|
731
|
-
for (const [fileIndex, file] of candidate.manifest.files.entries()) {
|
|
732
|
-
const parts = candidate.files[fileIndex];
|
|
733
|
-
if (!parts || parts.length !== file.chunks || parts.some((part) => part === null)) {
|
|
734
|
-
return null;
|
|
735
|
-
}
|
|
736
|
-
const bytes = new Uint8Array(file.size);
|
|
737
|
-
let offset = 0;
|
|
738
|
-
for (const part of parts) {
|
|
739
|
-
if (!part) {
|
|
740
|
-
return null;
|
|
741
|
-
}
|
|
742
|
-
bytes.set(part, offset);
|
|
743
|
-
offset += part.length;
|
|
744
|
-
}
|
|
745
|
-
if (offset !== file.size) {
|
|
746
|
-
return null;
|
|
747
|
-
}
|
|
748
|
-
const digest = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
749
|
-
if (digest !== file.sha256) {
|
|
750
|
-
return null;
|
|
751
|
-
}
|
|
752
|
-
if (file.path === "client.js") {
|
|
753
|
-
source = new TextDecoder().decode(bytes);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return source;
|
|
757
|
-
}
|
|
758
|
-
function createPreviewManager(baseUrl, onEnded, onViews) {
|
|
759
|
-
const entries = /* @__PURE__ */ new Map();
|
|
760
|
-
const notify = (entry) => {
|
|
761
|
-
for (const listener of entry.listeners) {
|
|
762
|
-
listener();
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
const close = (entry) => {
|
|
766
|
-
if (entry.reconnect) {
|
|
767
|
-
clearTimeout(entry.reconnect);
|
|
768
|
-
}
|
|
769
|
-
entry.reconnect = null;
|
|
770
|
-
const socket = entry.socket;
|
|
771
|
-
entry.socket = null;
|
|
772
|
-
socket?.close();
|
|
773
|
-
entry.connecting = false;
|
|
774
|
-
entry.candidate = null;
|
|
775
|
-
};
|
|
776
|
-
const end = (entry) => {
|
|
777
|
-
close(entry);
|
|
778
|
-
entry.metadata = null;
|
|
779
|
-
entry.snapshot = empty;
|
|
780
|
-
notify(entry);
|
|
781
|
-
onEnded();
|
|
782
|
-
};
|
|
783
|
-
const handleEvent = async (entry, event) => {
|
|
784
|
-
if (event.type === "ended") {
|
|
785
|
-
end(entry);
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
if (event.revision <= entry.snapshot.revision) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
if (event.type === "begin") {
|
|
792
|
-
entry.candidate = {
|
|
793
|
-
revision: event.revision,
|
|
794
|
-
manifest: event.manifest,
|
|
795
|
-
files: event.manifest.files.map((file) => Array.from({ length: file.chunks }, () => null))
|
|
796
|
-
};
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
const candidate = entry.candidate;
|
|
800
|
-
if (!candidate || candidate.revision !== event.revision) {
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
if (event.type === "chunk") {
|
|
804
|
-
const file = candidate.manifest.files[event.fileIndex];
|
|
805
|
-
const slots = candidate.files[event.fileIndex];
|
|
806
|
-
if (!file || !slots || event.chunkIndex < 0 || event.chunkIndex >= slots.length || slots[event.chunkIndex]) {
|
|
807
|
-
entry.candidate = null;
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
const bytes = bytesFromBase64(event.base64);
|
|
811
|
-
if (bytes.length !== Math.min(256 * 1024, file.size - event.chunkIndex * 256 * 1024)) {
|
|
812
|
-
entry.candidate = null;
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
slots[event.chunkIndex] = bytes;
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
if (event.type === "complete") {
|
|
819
|
-
const source = await verify(candidate);
|
|
820
|
-
if (source !== null && entry.candidate === candidate && candidate.revision > entry.snapshot.revision) {
|
|
821
|
-
entry.snapshot = { revision: candidate.revision, source };
|
|
822
|
-
if (candidate.manifest.views !== void 0)
|
|
823
|
-
onViews?.(entry.app.id, candidate.manifest.views);
|
|
824
|
-
notify(entry);
|
|
825
|
-
}
|
|
826
|
-
if (entry.candidate === candidate) {
|
|
827
|
-
entry.candidate = null;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
const connect = async (entry) => {
|
|
832
|
-
if (!entry.listeners.size || !entry.app.preview || entry.socket || entry.connecting) {
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
entry.connecting = true;
|
|
836
|
-
const appMetadata = entry.app.preview;
|
|
837
|
-
let metadata = entry.metadata && viewerTokenExpiresAt(entry.metadata) > Math.max(Date.now(), viewerTokenExpiresAt(appMetadata)) ? entry.metadata : appMetadata;
|
|
838
|
-
try {
|
|
839
|
-
const refresh = new URL("preview/metadata", baseUrl);
|
|
840
|
-
refresh.searchParams.set("sessionId", metadata.sessionId);
|
|
841
|
-
const response = await fetch(refresh, { credentials: "same-origin" });
|
|
842
|
-
if (response.ok) {
|
|
843
|
-
metadata = previewMetadataSchema.parse(await response.json());
|
|
844
|
-
entry.metadata = metadata;
|
|
845
|
-
} else if (response.status === 404) {
|
|
846
|
-
end(entry);
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
} catch {
|
|
850
|
-
}
|
|
851
|
-
if (!entry.listeners.size) {
|
|
852
|
-
entry.connecting = false;
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
const socket = new WebSocket(metadata.websocketUrl, metadata.token);
|
|
856
|
-
entry.socket = socket;
|
|
857
|
-
entry.connecting = false;
|
|
858
|
-
socket.addEventListener("open", () => {
|
|
859
|
-
entry.delay = 1e3;
|
|
860
|
-
const client = createPreviewWebSocketClient(socket);
|
|
861
|
-
void (async () => {
|
|
862
|
-
try {
|
|
863
|
-
const stream = await client.subscribe();
|
|
864
|
-
for await (const event of stream) {
|
|
865
|
-
await handleEvent(entry, event);
|
|
866
|
-
}
|
|
867
|
-
} catch {
|
|
868
|
-
} finally {
|
|
869
|
-
socket.close();
|
|
870
|
-
}
|
|
871
|
-
})();
|
|
872
|
-
});
|
|
873
|
-
socket.addEventListener("error", () => socket.close());
|
|
874
|
-
socket.addEventListener("close", () => {
|
|
875
|
-
if (entry.socket !== socket) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
entry.socket = null;
|
|
879
|
-
entry.candidate = null;
|
|
880
|
-
if (entry.listeners.size) {
|
|
881
|
-
entry.reconnect = setTimeout(() => {
|
|
882
|
-
entry.reconnect = null;
|
|
883
|
-
void connect(entry);
|
|
884
|
-
}, entry.delay);
|
|
885
|
-
entry.delay = Math.min(entry.delay * 2, 3e4);
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
|
-
};
|
|
889
|
-
return {
|
|
890
|
-
getSnapshot: (sessionId) => entries.get(sessionId)?.snapshot ?? empty,
|
|
891
|
-
updateApp: (app) => {
|
|
892
|
-
const sessionId = app.preview?.sessionId;
|
|
893
|
-
const entry = sessionId && entries.get(sessionId);
|
|
894
|
-
if (entry) {
|
|
895
|
-
entry.app = app;
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
subscribe: (app, listener) => {
|
|
899
|
-
const sessionId = app.preview?.sessionId;
|
|
900
|
-
if (!sessionId) {
|
|
901
|
-
return () => {
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
let entry = entries.get(sessionId);
|
|
905
|
-
if (!entry) {
|
|
906
|
-
entry = {
|
|
907
|
-
app,
|
|
908
|
-
metadata: null,
|
|
909
|
-
candidate: null,
|
|
910
|
-
listeners: /* @__PURE__ */ new Set(),
|
|
911
|
-
snapshot: empty,
|
|
912
|
-
socket: null,
|
|
913
|
-
connecting: false,
|
|
914
|
-
reconnect: null,
|
|
915
|
-
delay: 1e3
|
|
916
|
-
};
|
|
917
|
-
entries.set(sessionId, entry);
|
|
918
|
-
}
|
|
919
|
-
entry.app = app;
|
|
920
|
-
entry.listeners.add(listener);
|
|
921
|
-
void connect(entry);
|
|
922
|
-
return () => {
|
|
923
|
-
entry?.listeners.delete(listener);
|
|
924
|
-
if (entry && !entry.listeners.size) {
|
|
925
|
-
close(entry);
|
|
926
|
-
entries.delete(sessionId);
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
},
|
|
930
|
-
dispose: () => {
|
|
931
|
-
for (const entry of entries.values()) {
|
|
932
|
-
close(entry);
|
|
933
|
-
}
|
|
934
|
-
entries.clear();
|
|
935
|
-
}
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// src/store.ts
|
|
940
|
-
function createTailorKitStore(baseUrlInput, initialApps) {
|
|
941
|
-
const baseUrl = toBaseUrl(baseUrlInput);
|
|
942
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
943
|
-
let providedApps = initialApps;
|
|
944
|
-
let appsSnapshot = {
|
|
945
|
-
apps: initialApps ?? [],
|
|
946
|
-
views: listViews(initialApps ?? []),
|
|
947
|
-
error: null,
|
|
948
|
-
status: initialApps === void 0 ? "idle" : "ready"
|
|
949
|
-
};
|
|
950
|
-
let appsPromise = null;
|
|
951
|
-
let appsRequestId = 0;
|
|
952
|
-
let appsRequested = false;
|
|
953
|
-
let appsSubscribers = 0;
|
|
954
|
-
let metaSnapshot = {
|
|
955
|
-
assetsBaseUrl: null,
|
|
956
|
-
error: null,
|
|
957
|
-
schema: null,
|
|
958
|
-
status: "idle"
|
|
959
|
-
};
|
|
960
|
-
let fetchMetaPromise = null;
|
|
961
|
-
let metaRequestId = 0;
|
|
962
|
-
const emit = () => {
|
|
963
|
-
for (const listener of listeners) {
|
|
964
|
-
listener();
|
|
965
|
-
}
|
|
966
|
-
};
|
|
967
|
-
const store = {
|
|
968
|
-
baseUrl,
|
|
969
|
-
views: createViewRegistry(),
|
|
970
|
-
setProvidedApps(apps) {
|
|
971
|
-
if (providedApps === apps) {
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
providedApps = apps;
|
|
975
|
-
appsRequestId += 1;
|
|
976
|
-
appsPromise = null;
|
|
977
|
-
appsSnapshot = {
|
|
978
|
-
apps: apps ?? [],
|
|
979
|
-
views: listViews(apps ?? []),
|
|
980
|
-
error: null,
|
|
981
|
-
status: apps === void 0 ? "idle" : "ready"
|
|
982
|
-
};
|
|
983
|
-
emit();
|
|
984
|
-
if (apps === void 0 && appsRequested && appsSubscribers > 0) {
|
|
985
|
-
void store.fetchApps();
|
|
986
|
-
}
|
|
987
|
-
},
|
|
988
|
-
fetchApps: (options = {}) => {
|
|
989
|
-
appsRequested = true;
|
|
990
|
-
if (providedApps !== void 0) {
|
|
991
|
-
return Promise.resolve();
|
|
992
|
-
}
|
|
993
|
-
if (appsPromise && !options.force) {
|
|
994
|
-
return appsPromise;
|
|
995
|
-
}
|
|
996
|
-
appsSnapshot = { ...appsSnapshot, error: null, status: "loading" };
|
|
997
|
-
emit();
|
|
998
|
-
const requestId = ++appsRequestId;
|
|
999
|
-
appsPromise = fetch(new URL("apps", baseUrl)).then(async (response) => {
|
|
1000
|
-
if (!response.ok) {
|
|
1001
|
-
throw new Error(`Unable to fetch TailorKit apps from ${baseUrl.toString()}.`);
|
|
1002
|
-
}
|
|
1003
|
-
const apps = await response.json();
|
|
1004
|
-
if (requestId !== appsRequestId) return;
|
|
1005
|
-
appsSnapshot = { apps, views: listViews(apps), error: null, status: "ready" };
|
|
1006
|
-
emit();
|
|
1007
|
-
}).catch((error) => {
|
|
1008
|
-
if (requestId !== appsRequestId) return;
|
|
1009
|
-
appsSnapshot = {
|
|
1010
|
-
...appsSnapshot,
|
|
1011
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1012
|
-
status: "error"
|
|
1013
|
-
};
|
|
1014
|
-
emit();
|
|
1015
|
-
});
|
|
1016
|
-
return appsPromise;
|
|
1017
|
-
},
|
|
1018
|
-
fetchMeta: (options = {}) => {
|
|
1019
|
-
if (fetchMetaPromise && !options.force) {
|
|
1020
|
-
return fetchMetaPromise;
|
|
1021
|
-
}
|
|
1022
|
-
metaSnapshot = { ...metaSnapshot, error: null, status: "loading" };
|
|
1023
|
-
emit();
|
|
1024
|
-
const requestId = ++metaRequestId;
|
|
1025
|
-
fetchMetaPromise = fetch(new URL("meta", baseUrl)).then(async (response) => {
|
|
1026
|
-
if (!response.ok) {
|
|
1027
|
-
throw new Error(`Unable to fetch TailorKit metadata from ${baseUrl.toString()}.`);
|
|
1028
|
-
}
|
|
1029
|
-
const meta = await response.json();
|
|
1030
|
-
if (requestId !== metaRequestId) return;
|
|
1031
|
-
metaSnapshot = {
|
|
1032
|
-
assetsBaseUrl: meta.assetsBaseUrl ?? null,
|
|
1033
|
-
error: null,
|
|
1034
|
-
schema: meta.schema,
|
|
1035
|
-
status: "ready"
|
|
1036
|
-
};
|
|
1037
|
-
emit();
|
|
1038
|
-
}).catch((error) => {
|
|
1039
|
-
if (requestId !== metaRequestId) return;
|
|
1040
|
-
metaSnapshot = {
|
|
1041
|
-
...metaSnapshot,
|
|
1042
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1043
|
-
status: "error"
|
|
1044
|
-
};
|
|
1045
|
-
emit();
|
|
1046
|
-
});
|
|
1047
|
-
return fetchMetaPromise;
|
|
1048
|
-
},
|
|
1049
|
-
getAppsSnapshot: () => appsSnapshot,
|
|
1050
|
-
getMetaSnapshot: () => metaSnapshot,
|
|
1051
|
-
subscribe: (listener) => {
|
|
1052
|
-
listeners.add(listener);
|
|
1053
|
-
return () => {
|
|
1054
|
-
listeners.delete(listener);
|
|
1055
|
-
};
|
|
1056
|
-
},
|
|
1057
|
-
subscribeApps: (listener) => {
|
|
1058
|
-
appsSubscribers += 1;
|
|
1059
|
-
listeners.add(listener);
|
|
1060
|
-
return () => {
|
|
1061
|
-
listeners.delete(listener);
|
|
1062
|
-
appsSubscribers -= 1;
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
return {
|
|
1067
|
-
...store,
|
|
1068
|
-
previews: createPreviewManager(
|
|
1069
|
-
baseUrl,
|
|
1070
|
-
() => {
|
|
1071
|
-
if (appsRequested) void store.fetchApps({ force: true });
|
|
1072
|
-
},
|
|
1073
|
-
(appId, views) => {
|
|
1074
|
-
const apps = appsSnapshot.apps.map((app) => app.id === appId ? { ...app, views } : app);
|
|
1075
|
-
appsSnapshot = { ...appsSnapshot, apps, views: listViews(apps) };
|
|
1076
|
-
emit();
|
|
1077
|
-
}
|
|
1078
|
-
)
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
function toBaseUrl(value) {
|
|
1082
|
-
const url = value instanceof URL ? new URL(value) : new URL(value);
|
|
1083
|
-
if (!url.pathname.endsWith("/")) {
|
|
1084
|
-
url.pathname = `${url.pathname}/`;
|
|
1085
|
-
}
|
|
1086
|
-
return url;
|
|
1087
|
-
}
|
|
1088
|
-
function listViews(apps) {
|
|
1089
|
-
return apps.flatMap(
|
|
1090
|
-
(app) => (app.views ?? []).filter((view) => !view.disabled).map(({ slot, path, instances }) => ({
|
|
1091
|
-
id: JSON.stringify([app.id, slot, path]),
|
|
1092
|
-
...instances ? { instances } : {},
|
|
1093
|
-
app,
|
|
1094
|
-
slot,
|
|
1095
|
-
path
|
|
1096
|
-
}))
|
|
1097
|
-
);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
520
|
// src/components/slot.tsx
|
|
521
|
+
import { toBaseUrl } from "@tailorkit/client-core";
|
|
1101
522
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1102
523
|
function ManagedSlot({ app, name, instanceKey }) {
|
|
1103
524
|
const { store } = useTailorRootContext("Slot");
|
|
1104
|
-
const state =
|
|
525
|
+
const state = useSyncExternalStore4(
|
|
1105
526
|
store.views.subscribe,
|
|
1106
527
|
store.views.getSnapshot,
|
|
1107
528
|
store.views.getSnapshot
|
|
@@ -1116,14 +537,16 @@ function InstanceSlot({
|
|
|
1116
537
|
state
|
|
1117
538
|
}) {
|
|
1118
539
|
const { store } = useTailorRootContext("Slot");
|
|
1119
|
-
const instances =
|
|
1120
|
-
const meta =
|
|
540
|
+
const instances = useAppSlotInstances(app, name);
|
|
541
|
+
const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
|
|
1121
542
|
const instance = instances.data?.find((instance2) => instance2.key === instanceKey);
|
|
1122
543
|
const resolved = instances.isSuccess && instance && meta.schema ? resolveSlotView(app.views ?? [], name, state, meta.schema) : null;
|
|
1123
544
|
const view = resolved?.status === "ready" ? resolved.view : void 0;
|
|
1124
545
|
const stableContext = useStableContext(resolved?.context);
|
|
1125
|
-
const stableInstance = useStableContext(
|
|
1126
|
-
|
|
546
|
+
const stableInstance = useStableContext(
|
|
547
|
+
instance ? { key: instance.key, metadata: instance.metadata, data: instance.data } : void 0
|
|
548
|
+
);
|
|
549
|
+
const readyState = useMemo5(
|
|
1127
550
|
() => view === void 0 || !stableInstance ? null : {
|
|
1128
551
|
controlled: true,
|
|
1129
552
|
view,
|
|
@@ -1135,6 +558,12 @@ function InstanceSlot({
|
|
|
1135
558
|
);
|
|
1136
559
|
if (instances.isPending) return /* @__PURE__ */ jsx3("div", { role: "status", children: "Loading view\u2026" });
|
|
1137
560
|
if (instances.error) return /* @__PURE__ */ jsx3("div", { role: "alert", children: instances.error.message });
|
|
561
|
+
if (meta.schema && meta.schema.slots[name]?.multiple !== true)
|
|
562
|
+
return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
563
|
+
'Slot "',
|
|
564
|
+
name,
|
|
565
|
+
'" does not support instances.'
|
|
566
|
+
] });
|
|
1138
567
|
if (!instance) return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
1139
568
|
'View instance "',
|
|
1140
569
|
instanceKey,
|
|
@@ -1154,7 +583,7 @@ function ControlledSlot({
|
|
|
1154
583
|
useTailorRootContext("Slot.Controlled");
|
|
1155
584
|
const stableContext = useStableContext(context);
|
|
1156
585
|
const stableInstance = useStableContext(instance);
|
|
1157
|
-
const state =
|
|
586
|
+
const state = useMemo5(
|
|
1158
587
|
() => ({
|
|
1159
588
|
controlled: true,
|
|
1160
589
|
view,
|
|
@@ -1175,15 +604,15 @@ var Slot = Object.assign(ManagedSlot, { Controlled: ControlledSlot });
|
|
|
1175
604
|
function SlotRenderer({ app, name, state }) {
|
|
1176
605
|
const { store, client } = useTailorRootContext("Slot");
|
|
1177
606
|
const reactId = useId2();
|
|
1178
|
-
const getBackendSession =
|
|
1179
|
-
() =>
|
|
1180
|
-
[store.
|
|
607
|
+
const getBackendSession = useMemo5(
|
|
608
|
+
() => store.client.endpoints.getSessionProvider(app),
|
|
609
|
+
[store.client, app.id, app.currentDeployment?.id, app.preview?.sessionId]
|
|
1181
610
|
);
|
|
1182
|
-
const meta =
|
|
611
|
+
const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
|
|
1183
612
|
const previewSessionId = app.preview?.sessionId ?? "";
|
|
1184
|
-
const appRef =
|
|
613
|
+
const appRef = useRef3(app);
|
|
1185
614
|
appRef.current = app;
|
|
1186
|
-
const subscribePreview =
|
|
615
|
+
const subscribePreview = useCallback4(
|
|
1187
616
|
(listener) => {
|
|
1188
617
|
const currentApp = appRef.current;
|
|
1189
618
|
return currentApp.preview?.sessionId === previewSessionId ? store.previews.subscribe(currentApp, listener) : () => {
|
|
@@ -1191,12 +620,12 @@ function SlotRenderer({ app, name, state }) {
|
|
|
1191
620
|
},
|
|
1192
621
|
[store, previewSessionId]
|
|
1193
622
|
);
|
|
1194
|
-
const getPreviewSnapshot =
|
|
623
|
+
const getPreviewSnapshot = useCallback4(
|
|
1195
624
|
() => store.previews.getSnapshot(previewSessionId),
|
|
1196
625
|
[store, previewSessionId]
|
|
1197
626
|
);
|
|
1198
|
-
const preview =
|
|
1199
|
-
const props =
|
|
627
|
+
const preview = useSyncExternalStore4(subscribePreview, getPreviewSnapshot, getPreviewSnapshot);
|
|
628
|
+
const props = useMemo5(
|
|
1200
629
|
() => ({
|
|
1201
630
|
...state,
|
|
1202
631
|
slot: name,
|
|
@@ -1205,7 +634,7 @@ function SlotRenderer({ app, name, state }) {
|
|
|
1205
634
|
}),
|
|
1206
635
|
[state, name, meta.schema]
|
|
1207
636
|
);
|
|
1208
|
-
const appUrl =
|
|
637
|
+
const appUrl = useMemo5(() => {
|
|
1209
638
|
if (app.clientPath) return new URL(app.clientPath, store.baseUrl);
|
|
1210
639
|
if (!meta.assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) return null;
|
|
1211
640
|
return new URL(
|
|
@@ -1213,16 +642,33 @@ function SlotRenderer({ app, name, state }) {
|
|
|
1213
642
|
toBaseUrl(meta.assetsBaseUrl)
|
|
1214
643
|
);
|
|
1215
644
|
}, [app, store.baseUrl, meta.assetsBaseUrl]);
|
|
1216
|
-
|
|
645
|
+
useEffect4(() => {
|
|
1217
646
|
void store.fetchMeta();
|
|
1218
647
|
}, [store]);
|
|
1219
|
-
|
|
648
|
+
useEffect4(() => {
|
|
1220
649
|
store.previews.updateApp(app);
|
|
1221
650
|
}, [store, app]);
|
|
1222
651
|
if (meta.schema === null || appUrl === null && preview.source === null) return null;
|
|
652
|
+
const multiple = meta.schema.slots[name]?.multiple === true;
|
|
653
|
+
if ("controlled" in state && state.status === "ready") {
|
|
654
|
+
if (multiple && !state.instance)
|
|
655
|
+
return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance is required. Pass instance to Slot.Controlled." });
|
|
656
|
+
if (!multiple && state.instance)
|
|
657
|
+
return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
658
|
+
'Slot "',
|
|
659
|
+
name,
|
|
660
|
+
'" does not support instances.'
|
|
661
|
+
] });
|
|
662
|
+
}
|
|
1223
663
|
if (!("controlled" in state)) {
|
|
1224
664
|
const selected = selectSlotView(app.views ?? [], name, state.view, meta.schema);
|
|
1225
|
-
if (selected?.instances)
|
|
665
|
+
if (selected?.instances && !multiple)
|
|
666
|
+
return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
667
|
+
'Slot "',
|
|
668
|
+
name,
|
|
669
|
+
'" does not support instances.'
|
|
670
|
+
] });
|
|
671
|
+
if (multiple)
|
|
1226
672
|
return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance key is required. Pass instanceKey to Slot." });
|
|
1227
673
|
}
|
|
1228
674
|
const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
|
|
@@ -1243,23 +689,24 @@ function SlotRenderer({ app, name, state }) {
|
|
|
1243
689
|
}
|
|
1244
690
|
|
|
1245
691
|
// src/tailorkit.tsx
|
|
692
|
+
import { createTailorKitFetchClient } from "@tailorkit/client-core";
|
|
1246
693
|
import { createElement as createElement2 } from "react";
|
|
1247
694
|
|
|
1248
695
|
// src/hooks/use-register-view.ts
|
|
1249
|
-
import { useEffect as
|
|
696
|
+
import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
|
|
1250
697
|
function useRegisterView(view, options) {
|
|
1251
698
|
const { store } = useTailorRootContext("useRegisterView");
|
|
1252
|
-
const id =
|
|
699
|
+
const id = useMemo6(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
|
|
1253
700
|
const status = options.status ?? "ready";
|
|
1254
701
|
const context = "context" in options ? options.context : void 0;
|
|
1255
702
|
const contextSnapshot = useStableContext(context);
|
|
1256
|
-
|
|
703
|
+
useEffect5(
|
|
1257
704
|
() => () => {
|
|
1258
705
|
store.views.unregister(id);
|
|
1259
706
|
},
|
|
1260
707
|
[id, store]
|
|
1261
708
|
);
|
|
1262
|
-
|
|
709
|
+
useEffect5(() => {
|
|
1263
710
|
store.views.register({
|
|
1264
711
|
context: contextSnapshot,
|
|
1265
712
|
id,
|
|
@@ -1269,70 +716,16 @@ function useRegisterView(view, options) {
|
|
|
1269
716
|
}, [contextSnapshot, id, view, status, store]);
|
|
1270
717
|
}
|
|
1271
718
|
|
|
1272
|
-
// src/hooks/use-apps.ts
|
|
1273
|
-
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
1274
|
-
|
|
1275
|
-
// src/scope-query.ts
|
|
1276
|
-
function normalizeScopeSelection(scopes) {
|
|
1277
|
-
if (scopes === void 0) {
|
|
1278
|
-
return { key: "*" };
|
|
1279
|
-
}
|
|
1280
|
-
const names = [...new Set(scopes)].sort();
|
|
1281
|
-
return { key: JSON.stringify(names), scopes: names };
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// src/hooks/use-apps.ts
|
|
1285
|
-
function useApps(options = {}) {
|
|
1286
|
-
return useAppsStore(useTailorRootContext("useApps").store, options);
|
|
1287
|
-
}
|
|
1288
|
-
function useAppsStore(store, options = {}) {
|
|
1289
|
-
const snapshot = useAppsSnapshot(store);
|
|
1290
|
-
const scopes = normalizeScopeSelection(options.scopes);
|
|
1291
|
-
const appIds = normalizeScopeSelection(options.appIds);
|
|
1292
|
-
const data = useMemo6(
|
|
1293
|
-
() => snapshot.status === "ready" ? snapshot.apps.filter((app) => matchesApp(app, scopes.scopes, appIds.scopes)) : void 0,
|
|
1294
|
-
[snapshot, scopes.key, appIds.key]
|
|
1295
|
-
);
|
|
1296
|
-
const refetch = useCallback4(() => store.fetchApps({ force: true }), [store]);
|
|
1297
|
-
return { ...toUseAppsResult(snapshot, refetch), data };
|
|
1298
|
-
}
|
|
1299
|
-
function matchesApp(app, scopes, appIds) {
|
|
1300
|
-
return (scopes === void 0 || app.scope !== void 0 && scopes.includes(app.scope.name)) && (appIds === void 0 || appIds.includes(app.id));
|
|
1301
|
-
}
|
|
1302
|
-
function useAppsSnapshot(store) {
|
|
1303
|
-
const snapshot = useSyncExternalStore4(
|
|
1304
|
-
store.subscribeApps,
|
|
1305
|
-
store.getAppsSnapshot,
|
|
1306
|
-
store.getAppsSnapshot
|
|
1307
|
-
);
|
|
1308
|
-
useEffect5(() => {
|
|
1309
|
-
void store.fetchApps();
|
|
1310
|
-
}, [store]);
|
|
1311
|
-
return snapshot;
|
|
1312
|
-
}
|
|
1313
|
-
function toUseAppsResult(snapshot, refetch) {
|
|
1314
|
-
return {
|
|
1315
|
-
data: snapshot.status === "ready" ? snapshot.apps : void 0,
|
|
1316
|
-
error: snapshot.error,
|
|
1317
|
-
isError: snapshot.status === "error",
|
|
1318
|
-
isLoading: snapshot.status === "loading",
|
|
1319
|
-
isPending: snapshot.status === "idle" || snapshot.status === "loading",
|
|
1320
|
-
isSuccess: snapshot.status === "ready",
|
|
1321
|
-
refetch,
|
|
1322
|
-
status: snapshot.status
|
|
1323
|
-
};
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
719
|
// src/hooks/use-views.ts
|
|
1327
720
|
import { useCallback as useCallback5, useMemo as useMemo7 } from "react";
|
|
1328
721
|
function useViews(options = {}) {
|
|
1329
722
|
const { store } = useTailorRootContext("useViews");
|
|
1330
|
-
const snapshot = useAppsSnapshot(store);
|
|
723
|
+
const snapshot = useAppsSnapshot(store, options);
|
|
1331
724
|
const scopes = normalizeScopeSelection(options.scopes);
|
|
1332
725
|
const appIds = normalizeScopeSelection(options.appIds);
|
|
1333
726
|
const data = useMemo7(
|
|
1334
727
|
() => snapshot.status === "ready" ? snapshot.views.filter(
|
|
1335
|
-
(view) => (options.slot === void 0 || view.slot === options.slot) &&
|
|
728
|
+
(view) => (options.slot === void 0 || view.slot === options.slot) && matchesApp2(view.app, scopes.scopes, appIds.scopes)
|
|
1336
729
|
) : void 0,
|
|
1337
730
|
[snapshot, scopes.key, appIds.key, options.slot]
|
|
1338
731
|
);
|
|
@@ -1369,6 +762,11 @@ function createReactTailorKitClient(options) {
|
|
|
1369
762
|
}
|
|
1370
763
|
const clientConfig = {
|
|
1371
764
|
baseUrl: options.baseUrl,
|
|
765
|
+
fetchClient: createTailorKitFetchClient({
|
|
766
|
+
baseUrl: options.baseUrl,
|
|
767
|
+
cache: options.cache,
|
|
768
|
+
fetch: options.fetch
|
|
769
|
+
}),
|
|
1372
770
|
components: wrappedComponents,
|
|
1373
771
|
theme
|
|
1374
772
|
};
|
|
@@ -1377,14 +775,12 @@ function createReactTailorKitClient(options) {
|
|
|
1377
775
|
Slot: Object.assign(
|
|
1378
776
|
function ClientSlot(props) {
|
|
1379
777
|
useTailorRootContext("Slot", client);
|
|
1380
|
-
|
|
1381
|
-
return createElement2(TypedReactSlot, props);
|
|
778
|
+
return createElement2(Slot, props);
|
|
1382
779
|
},
|
|
1383
780
|
{
|
|
1384
781
|
Controlled: function ClientControlledSlot(props) {
|
|
1385
782
|
useTailorRootContext("Slot.Controlled", client);
|
|
1386
|
-
|
|
1387
|
-
return createElement2(TypedControlledSlot, props);
|
|
783
|
+
return createElement2(Slot.Controlled, props);
|
|
1388
784
|
}
|
|
1389
785
|
}
|
|
1390
786
|
),
|
|
@@ -1409,7 +805,8 @@ function createReactTailorKitClient(options) {
|
|
|
1409
805
|
}
|
|
1410
806
|
|
|
1411
807
|
// src/components/root.tsx
|
|
1412
|
-
import { useEffect as useEffect6, useMemo as useMemo9, useState as
|
|
808
|
+
import { useEffect as useEffect6, useMemo as useMemo9, useState as useState2 } from "react";
|
|
809
|
+
import { createTailorKitStore, toBaseUrl as toBaseUrl2 } from "@tailorkit/client-core";
|
|
1413
810
|
|
|
1414
811
|
// src/components/render.tsx
|
|
1415
812
|
import { cloneElement, createElement as createElement3, isValidElement, useMemo as useMemo8 } from "react";
|
|
@@ -1487,12 +884,16 @@ function isEventHandler(key) {
|
|
|
1487
884
|
// src/components/root.tsx
|
|
1488
885
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1489
886
|
function Root({ apps: appsProp, children, render, client, ...props }) {
|
|
1490
|
-
const baseUrl =
|
|
1491
|
-
const [
|
|
887
|
+
const baseUrl = toBaseUrl2(client.baseUrl).toString();
|
|
888
|
+
const [previous, setStore] = useState2(() => ({
|
|
889
|
+
store: createTailorKitStore(baseUrl, appsProp, client.fetchClient),
|
|
890
|
+
fetchClient: client.fetchClient
|
|
891
|
+
}));
|
|
892
|
+
const previousStore = previous.store;
|
|
1492
893
|
let store = previousStore;
|
|
1493
|
-
if (previousStore.baseUrl.toString() !== baseUrl) {
|
|
1494
|
-
store = createTailorKitStore(baseUrl, appsProp);
|
|
1495
|
-
setStore(store);
|
|
894
|
+
if (previousStore.baseUrl.toString() !== baseUrl || previous.fetchClient !== client.fetchClient) {
|
|
895
|
+
store = createTailorKitStore(baseUrl, appsProp, client.fetchClient);
|
|
896
|
+
setStore({ store, fetchClient: client.fetchClient });
|
|
1496
897
|
}
|
|
1497
898
|
useEffect6(() => {
|
|
1498
899
|
store.setProvidedApps(appsProp);
|