@tailorkit/react 0.1.0-beta.2 → 0.1.0-beta.22

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.js CHANGED
@@ -1,133 +1,76 @@
1
- // src/tailor-kit.tsx
2
- import { useEffect as useEffect4, useId as useId2, useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore3 } from "react";
1
+ // src/components/slot.tsx
2
+ import { useCallback as useCallback4, useEffect as useEffect4, useId as useId2, useMemo as useMemo5, useRef as useRef3, useSyncExternalStore as useSyncExternalStore4 } from "react";
3
3
 
4
- // src/components/root.tsx
5
- import { useMemo as useMemo2 } from "react";
6
-
7
- // src/components/render.tsx
8
- import { cloneElement, createElement, isValidElement, useMemo } from "react";
9
- function useRender({
10
- defaultTagName,
11
- props,
12
- render
13
- }) {
14
- return useMemo(() => {
15
- if (render) {
16
- if (!isValidElement(render)) {
17
- throw new Error("The render prop must be a valid React element.");
18
- }
19
- return cloneElement(render, mergeProps(render.props, props));
20
- }
21
- return createElement(defaultTagName, props);
22
- }, [defaultTagName, props, render]);
23
- }
24
- function mergeProps(...propSets) {
25
- const merged = {};
26
- for (const props of propSets) {
27
- if (!props) {
28
- continue;
29
- }
30
- for (const [key, value] of Object.entries(props)) {
31
- if (value === void 0) {
32
- continue;
33
- }
34
- const existing = merged[key];
35
- if (key === "className" && existing && value) {
36
- merged[key] = `${existing} ${value}`;
37
- continue;
38
- }
39
- if (key === "style" && existing && value) {
40
- merged[key] = {
41
- ...existing,
42
- ...value
43
- };
44
- continue;
45
- }
46
- if (key === "ref" && existing && value) {
47
- merged[key] = composeRefs(existing, value);
48
- continue;
49
- }
50
- if (isEventHandler(key) && typeof existing === "function" && typeof value === "function") {
51
- merged[key] = (...args) => {
52
- existing(...args);
53
- const event = args[0];
54
- if (!event?.defaultPrevented) {
55
- value(...args);
56
- }
57
- };
58
- continue;
59
- }
60
- merged[key] = value;
61
- }
62
- }
63
- return merged;
64
- }
65
- function composeRefs(...refs) {
66
- return (node) => {
67
- for (const ref of refs) {
68
- if (typeof ref === "function") {
69
- ref(node);
70
- } else if (ref) {
71
- ref.current = node;
72
- }
73
- }
74
- };
75
- }
76
- function isEventHandler(key) {
77
- return /^on[A-Z]/u.test(key);
4
+ // src/hooks/use-stable-context.ts
5
+ import { useRef } from "react";
6
+ function useStableContext(value) {
7
+ const key = JSON.stringify(value);
8
+ const ref = useRef({ key, value });
9
+ if (ref.current.key !== key) {
10
+ ref.current = { key, value };
11
+ }
12
+ return ref.current.value;
78
13
  }
79
14
 
15
+ // src/hooks/use-slot.ts
16
+ import { createSlotStore, matchesApp as matchesApp3, serializeCacheKey } from "@tailorkit/client-core";
17
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
18
+
80
19
  // src/components/context.ts
81
20
  import { createContext, useContext } from "react";
82
21
  var TailorRootContext = createContext(null);
83
- function useTailorRootContext(component) {
22
+ function useTailorRootContext(component, expectedClient) {
84
23
  const context = useContext(TailorRootContext);
85
24
  if (!context) {
86
- throw new Error(`${component} must be rendered inside tailor.Root.`);
25
+ throw new Error(`${component} must be rendered inside Root.`);
26
+ }
27
+ if (expectedClient && context.client !== expectedClient) {
28
+ throw new Error(
29
+ `${component} was created for a different TailorKit client than the one passed to Root.`
30
+ );
87
31
  }
88
32
  return context;
89
33
  }
90
34
 
91
- // src/components/root.tsx
92
- import { jsx } from "react/jsx-runtime";
93
- var EMPTY_APPS = [];
94
- function Root({ apps: appsProp, children, render, tailor, ...props }) {
95
- const appsResult = tailor.useApps();
96
- const apps = appsProp ?? appsResult.data ?? EMPTY_APPS;
97
- const context = useMemo2(
98
- () => ({
99
- apps
100
- }),
101
- [apps]
102
- );
103
- const element = render ? useRender({
104
- defaultTagName: "div",
105
- props: mergeProps({ children }, props),
106
- render
107
- }) : children;
108
- return /* @__PURE__ */ jsx(TailorRootContext.Provider, { value: context, children: element });
109
- }
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";
110
41
 
111
42
  // src/hooks/use-apps.ts
112
- import { useCallback, useEffect, useSyncExternalStore } from "react";
113
- function createUseApps(store) {
114
- return function useApps() {
115
- const snapshot = useSyncExternalStore(
116
- store.subscribe,
117
- store.getAppsSnapshot,
118
- store.getAppsSnapshot
119
- );
120
- useEffect(() => {
121
- void store.fetchApps();
122
- }, []);
123
- const refetch = useCallback(() => store.fetchApps({ force: true }), []);
124
- return toUseAppsResult(snapshot, refetch);
125
- };
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);
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(() => store.fetchApps({ force: true }), [store]);
56
+ return { ...toUseAppsResult(snapshot, refetch), data };
57
+ }
58
+ function useAppsSnapshot(store) {
59
+ const snapshot = useSyncExternalStore(
60
+ store.subscribeApps,
61
+ store.getAppsSnapshot,
62
+ store.getAppsSnapshot
63
+ );
64
+ useEffect(() => {
65
+ void store.fetchApps();
66
+ }, [store]);
67
+ return snapshot;
126
68
  }
127
69
  function toUseAppsResult(snapshot, refetch) {
128
70
  return {
129
71
  data: snapshot.status === "ready" ? snapshot.apps : void 0,
130
72
  error: snapshot.error,
73
+ isFetching: snapshot.isFetching,
131
74
  isError: snapshot.status === "error",
132
75
  isLoading: snapshot.status === "loading",
133
76
  isPending: snapshot.status === "idle" || snapshot.status === "loading",
@@ -137,41 +80,82 @@ function toUseAppsResult(snapshot, refetch) {
137
80
  };
138
81
  }
139
82
 
140
- // src/hooks/use-current-screen.ts
141
- import { useEffect as useEffect2, useMemo as useMemo3, useRef } from "react";
142
- function createUseCurrentScreen(store) {
143
- return function useCurrentScreen(options) {
144
- useTailorRootContext("tailor.useCurrentScreen");
145
- const id = useMemo3(() => /* @__PURE__ */ Symbol("tailorkit-current-screen"), []);
146
- const status = options.status ?? "ready";
147
- const context = "context" in options ? options.context : void 0;
148
- const contextKey = JSON.stringify(context);
149
- const contextRef = useRef({ key: contextKey, value: context });
150
- if (contextRef.current.key !== contextKey) {
151
- contextRef.current = { key: contextKey, value: context };
83
+ // src/hooks/use-slot.ts
84
+ function useSlot({ slot, scopes, appIds }) {
85
+ const { store } = useTailorRootContext("useSlot");
86
+ const apps = useApps({ scopes, appIds });
87
+ const items = useResolvedSlot(slot, apps.data, apps.status, apps.error);
88
+ const refetch = useCallback2(async () => {
89
+ const multiple = store.client.meta().getSnapshot().data?.schema.slots[slot]?.multiple;
90
+ if (apps.isSuccess && multiple === true) {
91
+ await items.refetch();
92
+ return;
152
93
  }
153
- const contextSnapshot = contextRef.current.value;
154
- useEffect2(
155
- () => () => {
156
- store.unregisterScreen(id);
157
- },
158
- [id]
159
- );
160
- useEffect2(() => {
161
- store.registerScreen({
162
- context: contextSnapshot,
163
- id,
164
- screen: options.screen,
165
- status
166
- });
167
- }, [contextSnapshot, id, options.screen, status]);
94
+ await apps.refetch();
95
+ let previousKey;
96
+ while (true) {
97
+ const discovery = store.getAppsSnapshot();
98
+ if (discovery.status !== "ready") return;
99
+ const schema = store.getMetaSnapshot().schema;
100
+ const options = {
101
+ apps: discovery.apps.filter((app) => matchesApp3(app, scopes, appIds)),
102
+ slot,
103
+ activeView: schema === null || schema.slots[slot]?.multiple === true ? store.views.getSnapshot() : null
104
+ };
105
+ const key = serializeCacheKey([options.apps, slot, options.activeView]);
106
+ if (key === previousKey) return;
107
+ previousKey = key;
108
+ await createSlotStore(store.client, options).fetch();
109
+ }
110
+ }, [store, slot, scopes, appIds, apps.isSuccess, apps.refetch, items.refetch]);
111
+ return { ...items, isFetching: apps.isFetching || items.isFetching, refetch };
112
+ }
113
+ function useAppSlot(app, slot) {
114
+ return useResolvedSlot(slot, [app], "ready", null);
115
+ }
116
+ function useResolvedSlot(slot, apps, appsStatus, appsError) {
117
+ const { store } = useTailorRootContext("useSlot");
118
+ const activeView = useSyncExternalStore2(
119
+ store.views.subscribe,
120
+ store.views.getSnapshot,
121
+ store.views.getSnapshot
122
+ );
123
+ const meta = useSyncExternalStore2(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
124
+ const options = useStableContext({
125
+ apps: apps ?? [],
126
+ slot,
127
+ activeView: meta.schema === null || meta.schema.slots[slot]?.multiple === true ? activeView : null
128
+ });
129
+ const query = useMemo2(
130
+ () => createSlotStore(store.client, {
131
+ ...options,
132
+ appsStatus,
133
+ appsError
134
+ }),
135
+ [store.client, options, appsStatus, appsError]
136
+ );
137
+ const snapshot = useSyncExternalStore2(query.subscribe, query.getSnapshot, query.getSnapshot);
138
+ useEffect2(() => {
139
+ void query.fetch();
140
+ }, [query]);
141
+ const refetch = useCallback2(() => query.fetch({ force: true }), [query]);
142
+ return {
143
+ ...snapshot,
144
+ isError: snapshot.status === "error",
145
+ isPending: snapshot.status === "idle" || snapshot.status === "loading",
146
+ isLoading: snapshot.status === "loading",
147
+ isSuccess: snapshot.status === "ready",
148
+ refetch
168
149
  };
169
150
  }
170
151
 
152
+ // src/slot-view.ts
153
+ import { resolveSlotView, selectSlotView } from "@tailorkit/client-core";
154
+
171
155
  // src/primitives.tsx
172
- import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo4 } from "react";
156
+ import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo3 } from "react";
173
157
  import { resolveTheme } from "@tailorkit/core/primitives/theme";
174
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
158
+ import { jsx, jsxs } from "react/jsx-runtime";
175
159
  var PrimitiveThemeContext = createContext2(null);
176
160
  var tokenGroups = [
177
161
  "space",
@@ -292,10 +276,10 @@ var buildPrimitiveCss = ({
292
276
  display,
293
277
  nodeId,
294
278
  props,
295
- screenId,
279
+ viewId,
296
280
  theme
297
281
  }) => {
298
- const selector = `[data-tailorkit-screen="${cssEscape(screenId)}"] [data-tailorkit-node="${cssEscape(
282
+ const selector = `[data-tailorkit-view="${cssEscape(viewId)}"] [data-tailorkit-node="${cssEscape(
299
283
  nodeId
300
284
  )}"]`;
301
285
  const base = [`display: ${display};`];
@@ -323,8 +307,8 @@ var buildPrimitiveCss = ({
323
307
  }
324
308
  return rules.join("\n");
325
309
  };
326
- var buildThemeCss = (screenId, theme) => {
327
- const selector = `[data-tailorkit-screen="${cssEscape(screenId)}"]`;
310
+ var buildThemeCss = (viewId, theme) => {
311
+ const selector = `[data-tailorkit-view="${cssEscape(viewId)}"]`;
328
312
  const declarations = [];
329
313
  const resolvedTheme = resolveTheme(theme);
330
314
  for (const group of tokenGroups) {
@@ -336,7 +320,7 @@ var buildThemeCss = (screenId, theme) => {
336
320
  };
337
321
  var usePrimitiveNodeId = () => {
338
322
  const id = useId();
339
- return useMemo4(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
323
+ return useMemo3(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
340
324
  };
341
325
  var Primitive = ({
342
326
  children,
@@ -351,150 +335,44 @@ var Primitive = ({
351
335
  display,
352
336
  nodeId,
353
337
  props,
354
- screenId: context.screenId,
338
+ viewId: context.viewId,
355
339
  theme: context.theme
356
340
  }) : "";
357
341
  return /* @__PURE__ */ jsxs(Element, { "data-tailorkit-node": nodeId, children: [
358
- css === "" ? null : /* @__PURE__ */ jsx2("style", { "data-tailorkit-node-style": nodeId, children: css }),
342
+ css === "" ? null : /* @__PURE__ */ jsx("style", { "data-tailorkit-node-style": nodeId, children: css }),
359
343
  children
360
344
  ] });
361
345
  };
362
346
  var primitives = {
363
- Box: ({ props, slots }) => /* @__PURE__ */ jsx2(Primitive, { display: "block", element: "div", props, children: slots.default }),
364
- Flex: ({ props, slots }) => /* @__PURE__ */ jsx2(Primitive, { display: "flex", element: "div", props, children: slots.default }),
365
- Grid: ({ props, slots }) => /* @__PURE__ */ jsx2(Primitive, { display: "grid", element: "div", props, children: slots.default }),
366
- Inline: ({ props, slots }) => /* @__PURE__ */ jsx2(Primitive, { display: "inline", element: "span", props, children: slots.default })
347
+ Box: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "block", element: "div", props, children }),
348
+ Flex: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "flex", element: "div", props, children }),
349
+ Grid: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "grid", element: "div", props, children }),
350
+ Inline: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "inline", element: "span", props, children })
367
351
  };
368
352
 
369
353
  // src/remote-view.tsx
370
354
  import {
371
355
  Component,
372
356
  Fragment,
373
- createElement as createElement2,
357
+ createElement,
374
358
  memo,
375
- useCallback as useCallback2,
359
+ useCallback as useCallback3,
376
360
  useContext as useContext3,
377
361
  useEffect as useEffect3,
378
- useMemo as useMemo5,
362
+ useMemo as useMemo4,
379
363
  useRef as useRef2,
380
364
  useState,
381
- useSyncExternalStore as useSyncExternalStore2
365
+ useSyncExternalStore as useSyncExternalStore3
382
366
  } from "react";
383
367
  import { createIframeUiHost } from "@tailorkit/sandbox/host";
384
-
385
- // src/node-store.ts
386
- var childIds = (children) => children.map((c) => c.id).join(",");
387
- var nodeSignature = (node) => {
388
- if (node.kind === "text") {
389
- return `text:${node.text}`;
390
- }
391
- if (node.kind === "fragment") {
392
- return `frag:${childIds(node.children)}`;
393
- }
394
- const propsStr = JSON.stringify(node.props);
395
- const callbacksStr = (node.callbacks ?? []).map((binding) => `${binding.callback}:${binding.event}`).join(",");
396
- return `elem:${node.type}|${childIds(node.children)}|${propsStr}|${callbacksStr}`;
397
- };
398
- var flattenTree = (node, out) => {
399
- out.set(node.id, node);
400
- if (node.kind === "text") {
401
- return;
402
- }
403
- for (const child of node.children) {
404
- flattenTree(child, out);
405
- }
406
- };
407
- var NodeStore = class {
408
- nodes = /* @__PURE__ */ new Map();
409
- signatures = /* @__PURE__ */ new Map();
410
- nodeListeners = /* @__PURE__ */ new Map();
411
- rootListeners = /* @__PURE__ */ new Set();
412
- rootId = null;
413
- clear() {
414
- const nodeIds = [...this.nodeListeners.keys()];
415
- const hadRoot = this.rootId !== null;
416
- this.nodes = /* @__PURE__ */ new Map();
417
- this.signatures = /* @__PURE__ */ new Map();
418
- this.rootId = null;
419
- for (const id of nodeIds) {
420
- const subs = this.nodeListeners.get(id);
421
- if (subs) {
422
- for (const l of subs) {
423
- l();
424
- }
425
- }
426
- }
427
- if (hadRoot) {
428
- for (const l of this.rootListeners) {
429
- l();
430
- }
431
- }
432
- }
433
- setSnapshot(tree) {
434
- const next = /* @__PURE__ */ new Map();
435
- flattenTree(tree, next);
436
- const changed = /* @__PURE__ */ new Set();
437
- for (const [id, node] of next) {
438
- const sig = nodeSignature(node);
439
- if (this.signatures.get(id) !== sig) {
440
- changed.add(id);
441
- this.signatures.set(id, sig);
442
- }
443
- }
444
- for (const id of this.signatures.keys()) {
445
- if (!next.has(id)) {
446
- this.signatures.delete(id);
447
- }
448
- }
449
- this.nodes = next;
450
- const prevRootId = this.rootId;
451
- this.rootId = tree.id;
452
- for (const id of changed) {
453
- const subs = this.nodeListeners.get(id);
454
- if (subs) {
455
- for (const l of subs) {
456
- l();
457
- }
458
- }
459
- }
460
- if (prevRootId !== this.rootId) {
461
- for (const l of this.rootListeners) {
462
- l();
463
- }
464
- }
465
- }
466
- getRootId() {
467
- return this.rootId;
468
- }
469
- getNode(id) {
470
- return this.nodes.get(id) ?? null;
471
- }
472
- subscribe(id, listener) {
473
- let subs = this.nodeListeners.get(id);
474
- if (!subs) {
475
- subs = /* @__PURE__ */ new Set();
476
- this.nodeListeners.set(id, subs);
477
- }
478
- subs.add(listener);
479
- return () => {
480
- subs.delete(listener);
481
- if (subs.size === 0) {
482
- this.nodeListeners.delete(id);
483
- }
484
- };
485
- }
486
- subscribeRoot(listener) {
487
- this.rootListeners.add(listener);
488
- return () => this.rootListeners.delete(listener);
489
- }
490
- };
368
+ import { NodeStore } from "@tailorkit/client-core";
491
369
 
492
370
  // src/remote-context.ts
493
371
  import { createContext as createContext3 } from "react";
494
372
  var RemoteUIContext = createContext3(null);
495
373
 
496
374
  // src/remote-view.tsx
497
- import { jsx as jsx3 } from "react/jsx-runtime";
375
+ import { jsx as jsx2 } from "react/jsx-runtime";
498
376
  var formatError = (error) => error.message;
499
377
  var RemoteErrorBoundary = class extends Component {
500
378
  state = { error: null };
@@ -506,17 +384,18 @@ var RemoteErrorBoundary = class extends Component {
506
384
  }
507
385
  render() {
508
386
  if (this.state.error) {
509
- return createElement2("div", null, formatError(this.state.error));
387
+ return createElement("div", null, formatError(this.state.error));
510
388
  }
511
389
  return this.props.children;
512
390
  }
513
391
  };
514
392
  function RemoteViewHost({
515
393
  appUrl,
394
+ sourceText,
395
+ getBackendSession,
516
396
  components: components2,
517
397
  createIframe,
518
- props,
519
- runtimeUrl
398
+ props
520
399
  }) {
521
400
  const appKey = appUrl.toString();
522
401
  const storeRef = useRef2(null);
@@ -529,6 +408,12 @@ function RemoteViewHost({
529
408
  appKeyRef.current = appKey;
530
409
  store.clear();
531
410
  }
411
+ const hostRef = useRef2(null);
412
+ const propsRef = useRef2(props);
413
+ useEffect3(() => {
414
+ propsRef.current = props;
415
+ hostRef.current?.setProps(props);
416
+ }, [props]);
532
417
  const dispatchRef = useRef2(null);
533
418
  const [error, setError] = useState(null);
534
419
  const [status, setStatus] = useState("starting");
@@ -540,29 +425,31 @@ function RemoteViewHost({
540
425
  useEffect3(() => {
541
426
  ctxRef.current.components = components2;
542
427
  }, [components2]);
543
- const ui = useMemo5(() => {
428
+ const ui = useMemo4(() => {
544
429
  const ctx = ctxRef.current;
545
- return createElement2(
430
+ return createElement(
546
431
  RemoteUIContext.Provider,
547
432
  { value: ctx },
548
- createElement2(
433
+ createElement(
549
434
  RemoteErrorBoundary,
550
435
  { key: appKey },
551
- createElement2(RemoteRoot, { store: ctx.store })
436
+ createElement(RemoteRoot, { store: ctx.store })
552
437
  )
553
438
  );
554
439
  }, [appKey]);
555
440
  useEffect3(() => {
556
441
  const host = createIframeUiHost(appUrl, {
557
442
  createIframe,
443
+ sourceText,
444
+ getBackendSession,
558
445
  onError: (error2) => {
559
446
  console.error("TailorKit remote app failed", error2);
560
447
  setError(error2);
561
448
  setStatus("error");
562
449
  },
563
- props,
564
- runtimeUrl
450
+ props: propsRef.current
565
451
  });
452
+ hostRef.current = host;
566
453
  dispatchRef.current = (payload) => host.dispatch(payload);
567
454
  const unsubscribe = host.subscribe(() => {
568
455
  const tree = host.getSnapshot();
@@ -577,26 +464,27 @@ function RemoteViewHost({
577
464
  return () => {
578
465
  unsubscribe();
579
466
  dispatchRef.current = null;
467
+ hostRef.current = null;
580
468
  host.destroy();
581
469
  };
582
- }, [appUrl, createIframe, props, runtimeUrl, store]);
470
+ }, [appUrl, createIframe, sourceText, getBackendSession, store]);
583
471
  if (status === "error" && error) {
584
- return createElement2("div", null, formatError(error));
472
+ return createElement("div", null, formatError(error));
585
473
  }
586
474
  return ui;
587
475
  }
588
476
  function RemoteRoot({ store }) {
589
- const subscribe = useCallback2((listener) => store.subscribeRoot(listener), [store]);
590
- const getSnapshot = useCallback2(() => store.getRootId(), [store]);
591
- const rootId = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
477
+ const subscribe = useCallback3((listener) => store.subscribeRoot(listener), [store]);
478
+ const getSnapshot = useCallback3(() => store.getRootId(), [store]);
479
+ const rootId = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
592
480
  if (rootId === null) {
593
481
  return null;
594
482
  }
595
- return /* @__PURE__ */ jsx3(RemoteView, { nodeId: rootId });
483
+ return /* @__PURE__ */ jsx2(RemoteView, { nodeId: rootId });
596
484
  }
597
485
  var RemoteElementView = memo(({ ctx, node }) => {
598
486
  const { dispatch, components: components2 } = ctx;
599
- const children = node.children.map((child) => /* @__PURE__ */ jsx3(RemoteView, { nodeId: child.id }, child.id));
487
+ const children = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
600
488
  const component = components2[node.type];
601
489
  if (component === void 0) {
602
490
  throw new Error(`TailorKit component "${node.type}" is not registered.`);
@@ -614,35 +502,236 @@ var RemoteElementView = memo(({ ctx, node }) => {
614
502
  });
615
503
  };
616
504
  }
617
- return createElement2(component, props, ...children);
505
+ return createElement(component, props, ...children);
618
506
  });
619
- var RemoteView = memo(function RemoteView2({ nodeId }) {
507
+ function RemoteViewComponent({ nodeId }) {
620
508
  const ctx = useContext3(RemoteUIContext);
621
509
  if (ctx === null) {
622
510
  return null;
623
511
  }
624
512
  const { store } = ctx;
625
- const subscribe = useCallback2(
513
+ const subscribe = useCallback3(
626
514
  (listener) => store.subscribe(nodeId, listener),
627
515
  [store, nodeId]
628
516
  );
629
- const getSnapshot = useCallback2(() => store.getNode(nodeId), [store, nodeId]);
630
- const node = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
517
+ const getSnapshot = useCallback3(() => store.getNode(nodeId), [store, nodeId]);
518
+ const node = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
631
519
  if (node === null) {
632
520
  return null;
633
521
  }
634
522
  if (node.kind === "text") {
635
523
  return node.text;
636
524
  }
637
- const childElements = node.children.map((child) => /* @__PURE__ */ jsx3(RemoteView2, { nodeId: child.id }, child.id));
525
+ const childElements = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
638
526
  if (node.kind === "fragment") {
639
- return createElement2(Fragment, { key: node.id }, ...childElements);
527
+ return createElement(Fragment, { key: node.id }, ...childElements);
640
528
  }
641
- return /* @__PURE__ */ jsx3(RemoteElementView, { node, ctx });
642
- });
529
+ return /* @__PURE__ */ jsx2(RemoteElementView, { node, ctx });
530
+ }
531
+ var RemoteView = memo(RemoteViewComponent);
532
+
533
+ // src/components/slot.tsx
534
+ import { toBaseUrl } from "@tailorkit/client-core";
535
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
536
+ function ManagedSlot({ app, name, instanceKey }) {
537
+ const { store } = useTailorRootContext("Slot");
538
+ const state = useSyncExternalStore4(
539
+ store.views.subscribe,
540
+ store.views.getSnapshot,
541
+ store.views.getSnapshot
542
+ );
543
+ if (state === null) return null;
544
+ return instanceKey === void 0 ? /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state }) : /* @__PURE__ */ jsx3(InstanceSlot, { app, name, instanceKey, state });
545
+ }
546
+ function InstanceSlot({
547
+ app,
548
+ name,
549
+ instanceKey,
550
+ state
551
+ }) {
552
+ const { store } = useTailorRootContext("Slot");
553
+ const instances = useAppSlot(app, name);
554
+ const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
555
+ const instance = instances.data?.find(
556
+ (item) => "key" in item && item.key === instanceKey
557
+ );
558
+ const resolved = instances.isSuccess && instance && meta.schema ? resolveSlotView(app.views ?? [], name, state, meta.schema) : null;
559
+ const view = resolved?.status === "ready" ? resolved.view : void 0;
560
+ const stableContext = useStableContext(resolved?.context);
561
+ const stableInstance = useStableContext(
562
+ instance ? { key: instance.key, metadata: instance.metadata, data: instance.data } : void 0
563
+ );
564
+ const readyState = useMemo5(
565
+ () => view === void 0 || !stableInstance ? null : {
566
+ controlled: true,
567
+ view,
568
+ context: stableContext,
569
+ status: "ready",
570
+ instance: stableInstance
571
+ },
572
+ [view, stableContext, stableInstance]
573
+ );
574
+ if (instances.isPending) return /* @__PURE__ */ jsx3("div", { role: "status", children: "Loading view\u2026" });
575
+ if (instances.error) return /* @__PURE__ */ jsx3("div", { role: "alert", children: instances.error.message });
576
+ if (meta.schema && meta.schema.slots[name]?.multiple !== true)
577
+ return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
578
+ 'Slot "',
579
+ name,
580
+ '" does not support instances.'
581
+ ] });
582
+ if (!instance) return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
583
+ 'View instance "',
584
+ instanceKey,
585
+ '" is unavailable.'
586
+ ] });
587
+ if (!readyState) return null;
588
+ return /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state: readyState });
589
+ }
590
+ function ControlledSlot({
591
+ app,
592
+ name,
593
+ view,
594
+ context,
595
+ status,
596
+ instance
597
+ }) {
598
+ useTailorRootContext("Slot.Controlled");
599
+ const stableContext = useStableContext(context);
600
+ const stableInstance = useStableContext(instance);
601
+ const state = useMemo5(
602
+ () => ({
603
+ controlled: true,
604
+ view,
605
+ context: stableContext,
606
+ status,
607
+ ...status === "ready" && stableInstance ? { instance: stableInstance } : {}
608
+ }),
609
+ [view, stableContext, status, stableInstance]
610
+ );
611
+ if (status === "ready" && !instance && app.views?.some(
612
+ (entry) => entry.slot === name && entry.path === view && entry.instances && !entry.disabled
613
+ )) {
614
+ return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance is required. Pass instance to Slot.Controlled." });
615
+ }
616
+ return /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state });
617
+ }
618
+ var Slot = Object.assign(ManagedSlot, { Controlled: ControlledSlot });
619
+ function SlotRenderer({ app, name, state }) {
620
+ const { store, client } = useTailorRootContext("Slot");
621
+ const reactId = useId2();
622
+ const getBackendSession = useMemo5(
623
+ () => store.client.endpoints.getSessionProvider(app),
624
+ [store.client, app.id, app.currentDeployment?.id, app.preview?.sessionId]
625
+ );
626
+ const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
627
+ const previewSessionId = app.preview?.sessionId ?? "";
628
+ const appRef = useRef3(app);
629
+ appRef.current = app;
630
+ const subscribePreview = useCallback4(
631
+ (listener) => {
632
+ const currentApp = appRef.current;
633
+ return currentApp.preview?.sessionId === previewSessionId ? store.previews.subscribe(currentApp, listener) : () => {
634
+ };
635
+ },
636
+ [store, previewSessionId]
637
+ );
638
+ const getPreviewSnapshot = useCallback4(
639
+ () => store.previews.getSnapshot(previewSessionId),
640
+ [store, previewSessionId]
641
+ );
642
+ const preview = useSyncExternalStore4(subscribePreview, getPreviewSnapshot, getPreviewSnapshot);
643
+ const props = useMemo5(
644
+ () => ({
645
+ ...state,
646
+ slot: name,
647
+ declaredViews: Object.keys(meta.schema?.views ?? {}),
648
+ supportedViews: meta.schema?.slots[name]?.views ?? []
649
+ }),
650
+ [state, name, meta.schema]
651
+ );
652
+ const appUrl = useMemo5(() => {
653
+ if (app.clientPath) return new URL(app.clientPath, store.baseUrl);
654
+ if (!meta.assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) return null;
655
+ return new URL(
656
+ `projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/client/client.js`,
657
+ toBaseUrl(meta.assetsBaseUrl)
658
+ );
659
+ }, [app, store.baseUrl, meta.assetsBaseUrl]);
660
+ useEffect4(() => {
661
+ void store.fetchMeta();
662
+ }, [store]);
663
+ useEffect4(() => {
664
+ store.previews.updateApp(app);
665
+ }, [store, app]);
666
+ if (meta.schema === null || appUrl === null && preview.source === null) return null;
667
+ const multiple = meta.schema.slots[name]?.multiple === true;
668
+ if ("controlled" in state && state.status === "ready") {
669
+ if (multiple && !state.instance)
670
+ return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance is required. Pass instance to Slot.Controlled." });
671
+ if (!multiple && state.instance)
672
+ return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
673
+ 'Slot "',
674
+ name,
675
+ '" does not support instances.'
676
+ ] });
677
+ }
678
+ if (!("controlled" in state)) {
679
+ const selected = selectSlotView(app.views ?? [], name, state.view, meta.schema);
680
+ if (selected?.instances && !multiple)
681
+ return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
682
+ 'Slot "',
683
+ name,
684
+ '" does not support instances.'
685
+ ] });
686
+ if (multiple)
687
+ return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance key is required. Pass instanceKey to Slot." });
688
+ }
689
+ const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
690
+ return /* @__PURE__ */ jsx3(PrimitiveThemeContext.Provider, { value: { viewId, theme: client.theme }, children: /* @__PURE__ */ jsxs2("div", { "data-tailorkit-view": viewId, children: [
691
+ /* @__PURE__ */ jsx3("style", { "data-tailorkit-theme-style": viewId, children: buildThemeCss(viewId, client.theme) }),
692
+ /* @__PURE__ */ jsx3(
693
+ RemoteViewHost,
694
+ {
695
+ appUrl: (appUrl ?? new URL(`preview/${previewSessionId}/client.js`, store.baseUrl)).toString(),
696
+ sourceText: preview.source ?? void 0,
697
+ getBackendSession,
698
+ components: client.components,
699
+ props
700
+ },
701
+ preview.revision || appUrl?.toString()
702
+ )
703
+ ] }) });
704
+ }
705
+
706
+ // src/tailorkit.tsx
707
+ import { createTailorKitFetchClient } from "@tailorkit/client-core";
708
+ import { createElement as createElement2 } from "react";
643
709
 
644
- // src/tailor-kit.tsx
645
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
710
+ // src/hooks/use-register-view.ts
711
+ import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
712
+ function useRegisterView(view, options) {
713
+ const { store } = useTailorRootContext("useRegisterView");
714
+ const id = useMemo6(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
715
+ const status = options.status ?? "ready";
716
+ const context = "context" in options ? options.context : void 0;
717
+ const contextSnapshot = useStableContext(context);
718
+ useEffect5(
719
+ () => () => {
720
+ store.views.unregister(id);
721
+ },
722
+ [id, store]
723
+ );
724
+ useEffect5(() => {
725
+ store.views.register({
726
+ context: contextSnapshot,
727
+ id,
728
+ view,
729
+ status
730
+ });
731
+ }, [contextSnapshot, id, view, status, store]);
732
+ }
733
+
734
+ // src/tailorkit.tsx
646
735
  var componentTagPrefix = "tailorkit-";
647
736
  var toComponentTagName = (name) => `${componentTagPrefix}${name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1-$2").replaceAll(/[\s_]+/gu, "-").toLowerCase()}`;
648
737
  function components(_schema, customComponents) {
@@ -653,7 +742,6 @@ function createTailorKitClient(options) {
653
742
  }
654
743
  function createReactTailorKitClient(options) {
655
744
  const wrappedComponents = {};
656
- const store = createTailorKitStore(options.baseUrl);
657
745
  const theme = options.theme ?? {};
658
746
  for (const [name, renderer] of Object.entries(options.components ?? {})) {
659
747
  if (renderer) {
@@ -663,257 +751,165 @@ function createReactTailorKitClient(options) {
663
751
  }) {
664
752
  return renderer({
665
753
  props,
666
- slots: { default: children }
754
+ children
667
755
  });
668
756
  };
669
757
  wrappedComponents[name] = TailorKitComponent;
670
758
  wrappedComponents[toComponentTagName(name)] = TailorKitComponent;
671
759
  }
672
760
  }
673
- const useApps = createUseApps(store);
674
- const useCurrentScreen = createUseCurrentScreen(store);
675
- const AppView = ({
676
- app,
677
- createIframe,
678
- fallback = null,
679
- runtimeUrl,
680
- ...screenProps
681
- }) => {
682
- const reactId = useId2();
683
- const currentScreen = useSyncExternalStore3(
684
- store.subscribe,
685
- store.getCurrentScreen,
686
- store.getCurrentScreen
687
- );
688
- const screen = screenProps.screen;
689
- const context = screenProps.context;
690
- const status = screenProps.status ?? "ready";
691
- const props = useMemo6(() => {
692
- if (screen !== void 0) {
693
- return {
694
- screen: {
695
- context,
696
- path: screen,
697
- status
698
- }
699
- };
700
- }
701
- return currentScreen === null ? void 0 : {
702
- screen: {
703
- context: currentScreen.context,
704
- path: currentScreen.screen,
705
- status: currentScreen.status
706
- }
707
- };
708
- }, [context, currentScreen, screen, status]);
709
- const meta = useSyncExternalStore3(
710
- store.subscribe,
711
- store.getMetaSnapshot,
712
- store.getMetaSnapshot
713
- );
714
- const assetsBaseUrl = app.clientPath ? null : meta.assetsBaseUrl;
715
- const appUrl = useMemo6(
716
- () => resolveAppUrl(app, store.baseUrl, assetsBaseUrl),
717
- [app, assetsBaseUrl]
718
- );
719
- useEffect4(() => {
720
- void store.fetchMeta();
721
- }, []);
722
- if (props === void 0 || appUrl === null) {
723
- return fallback;
724
- }
725
- const screenId = `tailorkit-screen-${reactId.replaceAll(":", "")}`;
726
- return /* @__PURE__ */ jsx4(PrimitiveThemeContext.Provider, { value: { screenId, theme }, children: /* @__PURE__ */ jsxs2("div", { "data-tailorkit-screen": screenId, children: [
727
- /* @__PURE__ */ jsx4("style", { "data-tailorkit-theme-style": screenId, children: buildThemeCss(screenId, theme) }),
728
- /* @__PURE__ */ jsx4(
729
- RemoteViewHost,
730
- {
731
- appUrl,
732
- components: wrappedComponents,
733
- createIframe,
734
- props,
735
- runtimeUrl
736
- }
737
- )
738
- ] }) });
761
+ const clientConfig = {
762
+ baseUrl: options.baseUrl,
763
+ fetchClient: createTailorKitFetchClient({
764
+ baseUrl: options.baseUrl,
765
+ cache: options.cache,
766
+ fetch: options.fetch
767
+ }),
768
+ components: wrappedComponents,
769
+ theme
739
770
  };
740
771
  const client = {
741
- AppView,
742
- Root: (props) => /* @__PURE__ */ jsx4(Root, { ...props, tailor: client }),
743
- getApp: store.getApp,
744
- getApps: store.getApps,
745
- useApps,
746
- useCurrentScreen
747
- };
748
- return {
749
- ...client
750
- };
751
- }
752
- function createTailorKitStore(baseUrlInput) {
753
- const baseUrl = toBaseUrl(baseUrlInput);
754
- const listeners = /* @__PURE__ */ new Set();
755
- const screens = /* @__PURE__ */ new Map();
756
- let appsSnapshot = {
757
- apps: [],
758
- error: null,
759
- status: "idle"
760
- };
761
- let metaSnapshot = {
762
- assetsBaseUrl: null,
763
- error: null,
764
- schema: null,
765
- status: "idle"
766
- };
767
- let currentScreen = null;
768
- let fetchAppsPromise = null;
769
- let fetchMetaPromise = null;
770
- let fetchAppsRequestId = 0;
771
- let nextOrder = 0;
772
- const emit = () => {
773
- for (const listener of listeners) {
774
- listener();
772
+ ...clientConfig,
773
+ Slot: Object.assign(
774
+ function ClientSlot(props) {
775
+ useTailorRootContext("Slot", client);
776
+ return createElement2(Slot, props);
777
+ },
778
+ {
779
+ Controlled: function ClientControlledSlot(props) {
780
+ useTailorRootContext("Slot.Controlled", client);
781
+ return createElement2(Slot.Controlled, props);
782
+ }
783
+ }
784
+ ),
785
+ useApps: function useClientApps(options2) {
786
+ useTailorRootContext("useApps", client);
787
+ return useApps(options2);
788
+ },
789
+ useSlot: function useClientSlot(options2) {
790
+ useTailorRootContext("useSlot", client);
791
+ return useSlot(options2);
792
+ },
793
+ useRegisterView: function useClientRegisterView(view, options2) {
794
+ useTailorRootContext("useRegisterView", client);
795
+ useRegisterView(view, options2);
775
796
  }
776
797
  };
777
- const selectCurrentScreen = () => {
778
- let selected = null;
779
- for (const screen of screens.values()) {
780
- const depth = getScreenDepth(screen.screen);
781
- const selectedDepth = selected === null ? -1 : getScreenDepth(selected.screen);
782
- if (selected === null || depth > selectedDepth) {
783
- selected = screen;
784
- }
785
- if (selected !== null && depth === selectedDepth && screen.order > selected.order) {
786
- selected = screen;
798
+ return client;
799
+ }
800
+
801
+ // src/components/root.tsx
802
+ import { useEffect as useEffect6, useMemo as useMemo8, useState as useState2 } from "react";
803
+ import { createTailorKitStore, toBaseUrl as toBaseUrl2 } from "@tailorkit/client-core";
804
+
805
+ // src/components/render.tsx
806
+ import { cloneElement, createElement as createElement3, isValidElement, useMemo as useMemo7 } from "react";
807
+ function useRender({
808
+ defaultTagName,
809
+ props,
810
+ render
811
+ }) {
812
+ return useMemo7(() => {
813
+ if (render) {
814
+ if (!isValidElement(render)) {
815
+ throw new Error("The render prop must be a valid React element.");
787
816
  }
817
+ return cloneElement(render, mergeProps(render.props, props));
788
818
  }
789
- const deepestScreens = selected === null ? [] : [...screens.values()].filter(
790
- (screen) => getScreenDepth(screen.screen) === getScreenDepth(selected.screen)
791
- );
792
- if (typeof console !== "undefined" && selected !== null && deepestScreens.length > 1) {
793
- const screenNames = deepestScreens.map((screen) => `"${screen.screen}"`).join(", ");
794
- console.warn(
795
- `TailorKit found multiple active screens at the same hierarchy depth: ${screenNames}. TailorKit selected "${selected.screen}" by mount order. Only one route at a hierarchy depth should call useCurrentScreen.`
796
- );
819
+ return createElement3(defaultTagName, props);
820
+ }, [defaultTagName, props, render]);
821
+ }
822
+ function mergeProps(...propSets) {
823
+ const merged = {};
824
+ for (const props of propSets) {
825
+ if (!props) {
826
+ continue;
797
827
  }
798
- currentScreen = selected;
799
- };
800
- return {
801
- baseUrl,
802
- fetchApps: (options = {}) => {
803
- if (fetchAppsPromise && !options.force) {
804
- return fetchAppsPromise;
828
+ for (const [key, value] of Object.entries(props)) {
829
+ if (value === void 0) {
830
+ continue;
805
831
  }
806
- appsSnapshot = { ...appsSnapshot, status: "loading" };
807
- emit();
808
- const requestId = ++fetchAppsRequestId;
809
- fetchAppsPromise = fetch(new URL("apps", baseUrl)).then(async (response) => {
810
- if (!response.ok) {
811
- throw new Error(`Unable to fetch TailorKit apps from ${baseUrl.toString()}.`);
812
- }
813
- const apps = await response.json();
814
- if (requestId !== fetchAppsRequestId) {
815
- return;
816
- }
817
- appsSnapshot = {
818
- apps,
819
- error: null,
820
- status: "ready"
821
- };
822
- emit();
823
- }).catch((error) => {
824
- if (requestId !== fetchAppsRequestId) {
825
- return;
826
- }
827
- appsSnapshot = {
828
- ...appsSnapshot,
829
- error: error instanceof Error ? error : new Error(String(error)),
830
- status: "error"
831
- };
832
- emit();
833
- });
834
- return fetchAppsPromise;
835
- },
836
- fetchMeta: () => {
837
- if (fetchMetaPromise) {
838
- return fetchMetaPromise;
832
+ const existing = merged[key];
833
+ if (key === "className" && existing && value) {
834
+ merged[key] = `${existing} ${value}`;
835
+ continue;
839
836
  }
840
- metaSnapshot = { ...metaSnapshot, status: "loading" };
841
- emit();
842
- fetchMetaPromise = fetch(new URL("meta", baseUrl)).then(async (response) => {
843
- if (!response.ok) {
844
- throw new Error(`Unable to fetch TailorKit metadata from ${baseUrl.toString()}.`);
845
- }
846
- const meta = await response.json();
847
- metaSnapshot = {
848
- assetsBaseUrl: meta.assetsBaseUrl ?? null,
849
- error: null,
850
- schema: meta.schema,
851
- status: "ready"
837
+ if (key === "style" && existing && value) {
838
+ merged[key] = {
839
+ ...existing,
840
+ ...value
852
841
  };
853
- emit();
854
- }).catch((error) => {
855
- metaSnapshot = {
856
- ...metaSnapshot,
857
- error: error instanceof Error ? error : new Error(String(error)),
858
- status: "error"
842
+ continue;
843
+ }
844
+ if (key === "ref" && existing && value) {
845
+ merged[key] = composeRefs(existing, value);
846
+ continue;
847
+ }
848
+ if (isEventHandler(key) && typeof existing === "function" && typeof value === "function") {
849
+ merged[key] = (...args) => {
850
+ existing(...args);
851
+ const event = args[0];
852
+ if (!event?.defaultPrevented) {
853
+ value(...args);
854
+ }
859
855
  };
860
- emit();
861
- });
862
- return fetchMetaPromise;
863
- },
864
- getApp: (id) => appsSnapshot.apps.find((app) => app.id === id),
865
- getApps: () => appsSnapshot.apps,
866
- getAppsSnapshot: () => appsSnapshot,
867
- getCurrentScreen: () => currentScreen,
868
- getMetaSnapshot: () => metaSnapshot,
869
- registerScreen: (entry) => {
870
- const existing = screens.get(entry.id);
871
- screens.set(entry.id, {
872
- ...entry,
873
- order: existing?.order ?? nextOrder
874
- });
875
- if (!existing) {
876
- nextOrder += 1;
856
+ continue;
857
+ }
858
+ merged[key] = value;
859
+ }
860
+ }
861
+ return merged;
862
+ }
863
+ function composeRefs(...refs) {
864
+ return (node) => {
865
+ for (const ref of refs) {
866
+ if (typeof ref === "function") {
867
+ ref(node);
868
+ } else if (ref) {
869
+ ref.current = node;
877
870
  }
878
- selectCurrentScreen();
879
- emit();
880
- },
881
- subscribe: (listener) => {
882
- listeners.add(listener);
883
- return () => {
884
- listeners.delete(listener);
885
- };
886
- },
887
- unregisterScreen: (id) => {
888
- screens.delete(id);
889
- selectCurrentScreen();
890
- emit();
891
871
  }
892
872
  };
893
873
  }
894
- function getScreenDepth(screen) {
895
- return screen === "/" ? 0 : screen.split("/").filter(Boolean).length;
874
+ function isEventHandler(key) {
875
+ return /^on[A-Z]/u.test(key);
896
876
  }
897
- function resolveAppUrl(app, baseUrl, assetsBaseUrl) {
898
- if (app.clientPath) {
899
- return new URL(app.clientPath, baseUrl);
900
- }
901
- if (!assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) {
902
- return null;
903
- }
904
- return new URL(
905
- `projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/files/client.js`,
906
- toBaseUrl(assetsBaseUrl)
877
+
878
+ // src/components/root.tsx
879
+ import { jsx as jsx4 } from "react/jsx-runtime";
880
+ function Root({ apps: appsProp, children, render, client, ...props }) {
881
+ const baseUrl = toBaseUrl2(client.baseUrl).toString();
882
+ const [previous, setStore] = useState2(() => ({
883
+ store: createTailorKitStore(baseUrl, appsProp, client.fetchClient),
884
+ fetchClient: client.fetchClient
885
+ }));
886
+ const previousStore = previous.store;
887
+ let store = previousStore;
888
+ if (previousStore.baseUrl.toString() !== baseUrl || previous.fetchClient !== client.fetchClient) {
889
+ store = createTailorKitStore(baseUrl, appsProp, client.fetchClient);
890
+ setStore({ store, fetchClient: client.fetchClient });
891
+ }
892
+ useEffect6(() => {
893
+ store.setProvidedApps(appsProp);
894
+ }, [store, appsProp]);
895
+ useEffect6(() => () => store.previews.dispose(), [store]);
896
+ const context = useMemo8(
897
+ () => ({
898
+ store,
899
+ client
900
+ }),
901
+ [store, client]
907
902
  );
908
- }
909
- function toBaseUrl(value) {
910
- const url = value instanceof URL ? new URL(value) : new URL(value);
911
- if (!url.pathname.endsWith("/")) {
912
- url.pathname = `${url.pathname}/`;
913
- }
914
- return url;
903
+ const element = render ? useRender({
904
+ defaultTagName: "div",
905
+ props: mergeProps({ children }, props),
906
+ render
907
+ }) : children;
908
+ return /* @__PURE__ */ jsx4(TailorRootContext.Provider, { value: context, children: element });
915
909
  }
916
910
  export {
911
+ Root,
912
+ Slot,
917
913
  components,
918
914
  createTailorKitClient,
919
915
  primitives