@tailorkit/react 0.1.0-beta.2 → 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/dist/index.js CHANGED
@@ -1,133 +1,79 @@
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-instances.ts
16
+ import { createSlotInstancesStore } 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, 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;
126
71
  }
127
72
  function toUseAppsResult(snapshot, refetch) {
128
73
  return {
129
74
  data: snapshot.status === "ready" ? snapshot.apps : void 0,
130
75
  error: snapshot.error,
76
+ isFetching: snapshot.isFetching,
131
77
  isError: snapshot.status === "error",
132
78
  isLoading: snapshot.status === "loading",
133
79
  isPending: snapshot.status === "idle" || snapshot.status === "loading",
@@ -137,41 +83,66 @@ function toUseAppsResult(snapshot, refetch) {
137
83
  };
138
84
  }
139
85
 
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 };
152
- }
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]);
86
+ // src/hooks/use-slot-instances.ts
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 } = {}) {
107
+ const { store } = useTailorRootContext("useSlotInstances");
108
+ const activeView = useSyncExternalStore2(
109
+ store.views.subscribe,
110
+ store.views.getSnapshot,
111
+ store.views.getSnapshot
112
+ );
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]
123
+ );
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]);
129
+ return {
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",
135
+ refetch
168
136
  };
169
137
  }
170
138
 
139
+ // src/slot-view.ts
140
+ import { resolveSlotView, selectSlotView } from "@tailorkit/client-core";
141
+
171
142
  // src/primitives.tsx
172
- import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo4 } from "react";
143
+ import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo3 } from "react";
173
144
  import { resolveTheme } from "@tailorkit/core/primitives/theme";
174
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
145
+ import { jsx, jsxs } from "react/jsx-runtime";
175
146
  var PrimitiveThemeContext = createContext2(null);
176
147
  var tokenGroups = [
177
148
  "space",
@@ -292,10 +263,10 @@ var buildPrimitiveCss = ({
292
263
  display,
293
264
  nodeId,
294
265
  props,
295
- screenId,
266
+ viewId,
296
267
  theme
297
268
  }) => {
298
- const selector = `[data-tailorkit-screen="${cssEscape(screenId)}"] [data-tailorkit-node="${cssEscape(
269
+ const selector = `[data-tailorkit-view="${cssEscape(viewId)}"] [data-tailorkit-node="${cssEscape(
299
270
  nodeId
300
271
  )}"]`;
301
272
  const base = [`display: ${display};`];
@@ -323,8 +294,8 @@ var buildPrimitiveCss = ({
323
294
  }
324
295
  return rules.join("\n");
325
296
  };
326
- var buildThemeCss = (screenId, theme) => {
327
- const selector = `[data-tailorkit-screen="${cssEscape(screenId)}"]`;
297
+ var buildThemeCss = (viewId, theme) => {
298
+ const selector = `[data-tailorkit-view="${cssEscape(viewId)}"]`;
328
299
  const declarations = [];
329
300
  const resolvedTheme = resolveTheme(theme);
330
301
  for (const group of tokenGroups) {
@@ -336,7 +307,7 @@ var buildThemeCss = (screenId, theme) => {
336
307
  };
337
308
  var usePrimitiveNodeId = () => {
338
309
  const id = useId();
339
- return useMemo4(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
310
+ return useMemo3(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
340
311
  };
341
312
  var Primitive = ({
342
313
  children,
@@ -351,150 +322,44 @@ var Primitive = ({
351
322
  display,
352
323
  nodeId,
353
324
  props,
354
- screenId: context.screenId,
325
+ viewId: context.viewId,
355
326
  theme: context.theme
356
327
  }) : "";
357
328
  return /* @__PURE__ */ jsxs(Element, { "data-tailorkit-node": nodeId, children: [
358
- css === "" ? null : /* @__PURE__ */ jsx2("style", { "data-tailorkit-node-style": nodeId, children: css }),
329
+ css === "" ? null : /* @__PURE__ */ jsx("style", { "data-tailorkit-node-style": nodeId, children: css }),
359
330
  children
360
331
  ] });
361
332
  };
362
333
  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 })
334
+ Box: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "block", element: "div", props, children }),
335
+ Flex: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "flex", element: "div", props, children }),
336
+ Grid: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "grid", element: "div", props, children }),
337
+ Inline: ({ props, children }) => /* @__PURE__ */ jsx(Primitive, { display: "inline", element: "span", props, children })
367
338
  };
368
339
 
369
340
  // src/remote-view.tsx
370
341
  import {
371
342
  Component,
372
343
  Fragment,
373
- createElement as createElement2,
344
+ createElement,
374
345
  memo,
375
- useCallback as useCallback2,
346
+ useCallback as useCallback3,
376
347
  useContext as useContext3,
377
348
  useEffect as useEffect3,
378
- useMemo as useMemo5,
349
+ useMemo as useMemo4,
379
350
  useRef as useRef2,
380
351
  useState,
381
- useSyncExternalStore as useSyncExternalStore2
352
+ useSyncExternalStore as useSyncExternalStore3
382
353
  } from "react";
383
354
  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
- };
355
+ import { NodeStore } from "@tailorkit/client-core";
491
356
 
492
357
  // src/remote-context.ts
493
358
  import { createContext as createContext3 } from "react";
494
359
  var RemoteUIContext = createContext3(null);
495
360
 
496
361
  // src/remote-view.tsx
497
- import { jsx as jsx3 } from "react/jsx-runtime";
362
+ import { jsx as jsx2 } from "react/jsx-runtime";
498
363
  var formatError = (error) => error.message;
499
364
  var RemoteErrorBoundary = class extends Component {
500
365
  state = { error: null };
@@ -506,17 +371,18 @@ var RemoteErrorBoundary = class extends Component {
506
371
  }
507
372
  render() {
508
373
  if (this.state.error) {
509
- return createElement2("div", null, formatError(this.state.error));
374
+ return createElement("div", null, formatError(this.state.error));
510
375
  }
511
376
  return this.props.children;
512
377
  }
513
378
  };
514
379
  function RemoteViewHost({
515
380
  appUrl,
381
+ sourceText,
382
+ getBackendSession,
516
383
  components: components2,
517
384
  createIframe,
518
- props,
519
- runtimeUrl
385
+ props
520
386
  }) {
521
387
  const appKey = appUrl.toString();
522
388
  const storeRef = useRef2(null);
@@ -529,6 +395,12 @@ function RemoteViewHost({
529
395
  appKeyRef.current = appKey;
530
396
  store.clear();
531
397
  }
398
+ const hostRef = useRef2(null);
399
+ const propsRef = useRef2(props);
400
+ useEffect3(() => {
401
+ propsRef.current = props;
402
+ hostRef.current?.setProps(props);
403
+ }, [props]);
532
404
  const dispatchRef = useRef2(null);
533
405
  const [error, setError] = useState(null);
534
406
  const [status, setStatus] = useState("starting");
@@ -540,29 +412,31 @@ function RemoteViewHost({
540
412
  useEffect3(() => {
541
413
  ctxRef.current.components = components2;
542
414
  }, [components2]);
543
- const ui = useMemo5(() => {
415
+ const ui = useMemo4(() => {
544
416
  const ctx = ctxRef.current;
545
- return createElement2(
417
+ return createElement(
546
418
  RemoteUIContext.Provider,
547
419
  { value: ctx },
548
- createElement2(
420
+ createElement(
549
421
  RemoteErrorBoundary,
550
422
  { key: appKey },
551
- createElement2(RemoteRoot, { store: ctx.store })
423
+ createElement(RemoteRoot, { store: ctx.store })
552
424
  )
553
425
  );
554
426
  }, [appKey]);
555
427
  useEffect3(() => {
556
428
  const host = createIframeUiHost(appUrl, {
557
429
  createIframe,
430
+ sourceText,
431
+ getBackendSession,
558
432
  onError: (error2) => {
559
433
  console.error("TailorKit remote app failed", error2);
560
434
  setError(error2);
561
435
  setStatus("error");
562
436
  },
563
- props,
564
- runtimeUrl
437
+ props: propsRef.current
565
438
  });
439
+ hostRef.current = host;
566
440
  dispatchRef.current = (payload) => host.dispatch(payload);
567
441
  const unsubscribe = host.subscribe(() => {
568
442
  const tree = host.getSnapshot();
@@ -577,26 +451,27 @@ function RemoteViewHost({
577
451
  return () => {
578
452
  unsubscribe();
579
453
  dispatchRef.current = null;
454
+ hostRef.current = null;
580
455
  host.destroy();
581
456
  };
582
- }, [appUrl, createIframe, props, runtimeUrl, store]);
457
+ }, [appUrl, createIframe, sourceText, getBackendSession, store]);
583
458
  if (status === "error" && error) {
584
- return createElement2("div", null, formatError(error));
459
+ return createElement("div", null, formatError(error));
585
460
  }
586
461
  return ui;
587
462
  }
588
463
  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);
464
+ const subscribe = useCallback3((listener) => store.subscribeRoot(listener), [store]);
465
+ const getSnapshot = useCallback3(() => store.getRootId(), [store]);
466
+ const rootId = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
592
467
  if (rootId === null) {
593
468
  return null;
594
469
  }
595
- return /* @__PURE__ */ jsx3(RemoteView, { nodeId: rootId });
470
+ return /* @__PURE__ */ jsx2(RemoteView, { nodeId: rootId });
596
471
  }
597
472
  var RemoteElementView = memo(({ ctx, node }) => {
598
473
  const { dispatch, components: components2 } = ctx;
599
- const children = node.children.map((child) => /* @__PURE__ */ jsx3(RemoteView, { nodeId: child.id }, child.id));
474
+ const children = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
600
475
  const component = components2[node.type];
601
476
  if (component === void 0) {
602
477
  throw new Error(`TailorKit component "${node.type}" is not registered.`);
@@ -614,35 +489,251 @@ var RemoteElementView = memo(({ ctx, node }) => {
614
489
  });
615
490
  };
616
491
  }
617
- return createElement2(component, props, ...children);
492
+ return createElement(component, props, ...children);
618
493
  });
619
- var RemoteView = memo(function RemoteView2({ nodeId }) {
494
+ function RemoteViewComponent({ nodeId }) {
620
495
  const ctx = useContext3(RemoteUIContext);
621
496
  if (ctx === null) {
622
497
  return null;
623
498
  }
624
499
  const { store } = ctx;
625
- const subscribe = useCallback2(
500
+ const subscribe = useCallback3(
626
501
  (listener) => store.subscribe(nodeId, listener),
627
502
  [store, nodeId]
628
503
  );
629
- const getSnapshot = useCallback2(() => store.getNode(nodeId), [store, nodeId]);
630
- const node = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
504
+ const getSnapshot = useCallback3(() => store.getNode(nodeId), [store, nodeId]);
505
+ const node = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
631
506
  if (node === null) {
632
507
  return null;
633
508
  }
634
509
  if (node.kind === "text") {
635
510
  return node.text;
636
511
  }
637
- const childElements = node.children.map((child) => /* @__PURE__ */ jsx3(RemoteView2, { nodeId: child.id }, child.id));
512
+ const childElements = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
638
513
  if (node.kind === "fragment") {
639
- return createElement2(Fragment, { key: node.id }, ...childElements);
514
+ return createElement(Fragment, { key: node.id }, ...childElements);
640
515
  }
641
- return /* @__PURE__ */ jsx3(RemoteElementView, { node, ctx });
642
- });
516
+ return /* @__PURE__ */ jsx2(RemoteElementView, { node, ctx });
517
+ }
518
+ var RemoteView = memo(RemoteViewComponent);
519
+
520
+ // src/components/slot.tsx
521
+ import { toBaseUrl } from "@tailorkit/client-core";
522
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
523
+ function ManagedSlot({ app, name, instanceKey }) {
524
+ const { store } = useTailorRootContext("Slot");
525
+ const state = useSyncExternalStore4(
526
+ store.views.subscribe,
527
+ store.views.getSnapshot,
528
+ store.views.getSnapshot
529
+ );
530
+ if (state === null) return null;
531
+ return instanceKey === void 0 ? /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state }) : /* @__PURE__ */ jsx3(InstanceSlot, { app, name, instanceKey, state });
532
+ }
533
+ function InstanceSlot({
534
+ app,
535
+ name,
536
+ instanceKey,
537
+ state
538
+ }) {
539
+ const { store } = useTailorRootContext("Slot");
540
+ const instances = useAppSlotInstances(app, name);
541
+ const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
542
+ const instance = instances.data?.find((instance2) => instance2.key === instanceKey);
543
+ const resolved = instances.isSuccess && instance && meta.schema ? resolveSlotView(app.views ?? [], name, state, meta.schema) : null;
544
+ const view = resolved?.status === "ready" ? resolved.view : void 0;
545
+ const stableContext = useStableContext(resolved?.context);
546
+ const stableInstance = useStableContext(
547
+ instance ? { key: instance.key, metadata: instance.metadata, data: instance.data } : void 0
548
+ );
549
+ const readyState = useMemo5(
550
+ () => view === void 0 || !stableInstance ? null : {
551
+ controlled: true,
552
+ view,
553
+ context: stableContext,
554
+ status: "ready",
555
+ instance: stableInstance
556
+ },
557
+ [view, stableContext, stableInstance]
558
+ );
559
+ if (instances.isPending) return /* @__PURE__ */ jsx3("div", { role: "status", children: "Loading view\u2026" });
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
+ ] });
567
+ if (!instance) return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
568
+ 'View instance "',
569
+ instanceKey,
570
+ '" is unavailable.'
571
+ ] });
572
+ if (!readyState) return null;
573
+ return /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state: readyState });
574
+ }
575
+ function ControlledSlot({
576
+ app,
577
+ name,
578
+ view,
579
+ context,
580
+ status,
581
+ instance
582
+ }) {
583
+ useTailorRootContext("Slot.Controlled");
584
+ const stableContext = useStableContext(context);
585
+ const stableInstance = useStableContext(instance);
586
+ const state = useMemo5(
587
+ () => ({
588
+ controlled: true,
589
+ view,
590
+ context: stableContext,
591
+ status,
592
+ ...status === "ready" && stableInstance ? { instance: stableInstance } : {}
593
+ }),
594
+ [view, stableContext, status, stableInstance]
595
+ );
596
+ if (status === "ready" && !instance && app.views?.some(
597
+ (entry) => entry.slot === name && entry.path === view && entry.instances && !entry.disabled
598
+ )) {
599
+ return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance is required. Pass instance to Slot.Controlled." });
600
+ }
601
+ return /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state });
602
+ }
603
+ var Slot = Object.assign(ManagedSlot, { Controlled: ControlledSlot });
604
+ function SlotRenderer({ app, name, state }) {
605
+ const { store, client } = useTailorRootContext("Slot");
606
+ const reactId = useId2();
607
+ const getBackendSession = useMemo5(
608
+ () => store.client.endpoints.getSessionProvider(app),
609
+ [store.client, app.id, app.currentDeployment?.id, app.preview?.sessionId]
610
+ );
611
+ const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
612
+ const previewSessionId = app.preview?.sessionId ?? "";
613
+ const appRef = useRef3(app);
614
+ appRef.current = app;
615
+ const subscribePreview = useCallback4(
616
+ (listener) => {
617
+ const currentApp = appRef.current;
618
+ return currentApp.preview?.sessionId === previewSessionId ? store.previews.subscribe(currentApp, listener) : () => {
619
+ };
620
+ },
621
+ [store, previewSessionId]
622
+ );
623
+ const getPreviewSnapshot = useCallback4(
624
+ () => store.previews.getSnapshot(previewSessionId),
625
+ [store, previewSessionId]
626
+ );
627
+ const preview = useSyncExternalStore4(subscribePreview, getPreviewSnapshot, getPreviewSnapshot);
628
+ const props = useMemo5(
629
+ () => ({
630
+ ...state,
631
+ slot: name,
632
+ declaredViews: Object.keys(meta.schema?.views ?? {}),
633
+ supportedViews: meta.schema?.slots[name]?.views ?? []
634
+ }),
635
+ [state, name, meta.schema]
636
+ );
637
+ const appUrl = useMemo5(() => {
638
+ if (app.clientPath) return new URL(app.clientPath, store.baseUrl);
639
+ if (!meta.assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) return null;
640
+ return new URL(
641
+ `projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/client/client.js`,
642
+ toBaseUrl(meta.assetsBaseUrl)
643
+ );
644
+ }, [app, store.baseUrl, meta.assetsBaseUrl]);
645
+ useEffect4(() => {
646
+ void store.fetchMeta();
647
+ }, [store]);
648
+ useEffect4(() => {
649
+ store.previews.updateApp(app);
650
+ }, [store, app]);
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
+ }
663
+ if (!("controlled" in state)) {
664
+ const selected = selectSlotView(app.views ?? [], name, state.view, meta.schema);
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)
672
+ return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance key is required. Pass instanceKey to Slot." });
673
+ }
674
+ const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
675
+ return /* @__PURE__ */ jsx3(PrimitiveThemeContext.Provider, { value: { viewId, theme: client.theme }, children: /* @__PURE__ */ jsxs2("div", { "data-tailorkit-view": viewId, children: [
676
+ /* @__PURE__ */ jsx3("style", { "data-tailorkit-theme-style": viewId, children: buildThemeCss(viewId, client.theme) }),
677
+ /* @__PURE__ */ jsx3(
678
+ RemoteViewHost,
679
+ {
680
+ appUrl: (appUrl ?? new URL(`preview/${previewSessionId}/client.js`, store.baseUrl)).toString(),
681
+ sourceText: preview.source ?? void 0,
682
+ getBackendSession,
683
+ components: client.components,
684
+ props
685
+ },
686
+ preview.revision || appUrl?.toString()
687
+ )
688
+ ] }) });
689
+ }
643
690
 
644
- // src/tailor-kit.tsx
645
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
691
+ // src/tailorkit.tsx
692
+ import { createTailorKitFetchClient } from "@tailorkit/client-core";
693
+ import { createElement as createElement2 } from "react";
694
+
695
+ // src/hooks/use-register-view.ts
696
+ import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
697
+ function useRegisterView(view, options) {
698
+ const { store } = useTailorRootContext("useRegisterView");
699
+ const id = useMemo6(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
700
+ const status = options.status ?? "ready";
701
+ const context = "context" in options ? options.context : void 0;
702
+ const contextSnapshot = useStableContext(context);
703
+ useEffect5(
704
+ () => () => {
705
+ store.views.unregister(id);
706
+ },
707
+ [id, store]
708
+ );
709
+ useEffect5(() => {
710
+ store.views.register({
711
+ context: contextSnapshot,
712
+ id,
713
+ view,
714
+ status
715
+ });
716
+ }, [contextSnapshot, id, view, status, store]);
717
+ }
718
+
719
+ // src/hooks/use-views.ts
720
+ import { useCallback as useCallback5, useMemo as useMemo7 } from "react";
721
+ function useViews(options = {}) {
722
+ const { store } = useTailorRootContext("useViews");
723
+ const snapshot = useAppsSnapshot(store, options);
724
+ const scopes = normalizeScopeSelection(options.scopes);
725
+ const appIds = normalizeScopeSelection(options.appIds);
726
+ const data = useMemo7(
727
+ () => snapshot.status === "ready" ? snapshot.views.filter(
728
+ (view) => (options.slot === void 0 || view.slot === options.slot) && matchesApp2(view.app, scopes.scopes, appIds.scopes)
729
+ ) : void 0,
730
+ [snapshot, scopes.key, appIds.key, options.slot]
731
+ );
732
+ const refetch = useCallback5(() => store.fetchApps({ force: true }), [store]);
733
+ return { ...toUseAppsResult(snapshot, refetch), data };
734
+ }
735
+
736
+ // src/tailorkit.tsx
646
737
  var componentTagPrefix = "tailorkit-";
647
738
  var toComponentTagName = (name) => `${componentTagPrefix}${name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1-$2").replaceAll(/[\s_]+/gu, "-").toLowerCase()}`;
648
739
  function components(_schema, customComponents) {
@@ -653,7 +744,6 @@ function createTailorKitClient(options) {
653
744
  }
654
745
  function createReactTailorKitClient(options) {
655
746
  const wrappedComponents = {};
656
- const store = createTailorKitStore(options.baseUrl);
657
747
  const theme = options.theme ?? {};
658
748
  for (const [name, renderer] of Object.entries(options.components ?? {})) {
659
749
  if (renderer) {
@@ -663,257 +753,169 @@ function createReactTailorKitClient(options) {
663
753
  }) {
664
754
  return renderer({
665
755
  props,
666
- slots: { default: children }
756
+ children
667
757
  });
668
758
  };
669
759
  wrappedComponents[name] = TailorKitComponent;
670
760
  wrappedComponents[toComponentTagName(name)] = TailorKitComponent;
671
761
  }
672
762
  }
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
- ] }) });
763
+ const clientConfig = {
764
+ baseUrl: options.baseUrl,
765
+ fetchClient: createTailorKitFetchClient({
766
+ baseUrl: options.baseUrl,
767
+ cache: options.cache,
768
+ fetch: options.fetch
769
+ }),
770
+ components: wrappedComponents,
771
+ theme
739
772
  };
740
773
  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();
774
+ ...clientConfig,
775
+ Slot: Object.assign(
776
+ function ClientSlot(props) {
777
+ useTailorRootContext("Slot", client);
778
+ return createElement2(Slot, props);
779
+ },
780
+ {
781
+ Controlled: function ClientControlledSlot(props) {
782
+ useTailorRootContext("Slot.Controlled", client);
783
+ return createElement2(Slot.Controlled, props);
784
+ }
785
+ }
786
+ ),
787
+ useApps: function useClientApps(options2) {
788
+ useTailorRootContext("useApps", client);
789
+ return useApps(options2);
790
+ },
791
+ useViews: function useClientViews(options2) {
792
+ useTailorRootContext("useViews", client);
793
+ return useViews(options2);
794
+ },
795
+ useSlotInstances: function useClientSlotInstances(options2) {
796
+ useTailorRootContext("useSlotInstances", client);
797
+ return useSlotInstances(options2);
798
+ },
799
+ useRegisterView: function useClientRegisterView(view, options2) {
800
+ useTailorRootContext("useRegisterView", client);
801
+ useRegisterView(view, options2);
775
802
  }
776
803
  };
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;
804
+ return client;
805
+ }
806
+
807
+ // src/components/root.tsx
808
+ import { useEffect as useEffect6, useMemo as useMemo9, useState as useState2 } from "react";
809
+ import { createTailorKitStore, toBaseUrl as toBaseUrl2 } from "@tailorkit/client-core";
810
+
811
+ // src/components/render.tsx
812
+ import { cloneElement, createElement as createElement3, isValidElement, useMemo as useMemo8 } from "react";
813
+ function useRender({
814
+ defaultTagName,
815
+ props,
816
+ render
817
+ }) {
818
+ return useMemo8(() => {
819
+ if (render) {
820
+ if (!isValidElement(render)) {
821
+ throw new Error("The render prop must be a valid React element.");
787
822
  }
823
+ return cloneElement(render, mergeProps(render.props, props));
788
824
  }
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
- );
825
+ return createElement3(defaultTagName, props);
826
+ }, [defaultTagName, props, render]);
827
+ }
828
+ function mergeProps(...propSets) {
829
+ const merged = {};
830
+ for (const props of propSets) {
831
+ if (!props) {
832
+ continue;
797
833
  }
798
- currentScreen = selected;
799
- };
800
- return {
801
- baseUrl,
802
- fetchApps: (options = {}) => {
803
- if (fetchAppsPromise && !options.force) {
804
- return fetchAppsPromise;
834
+ for (const [key, value] of Object.entries(props)) {
835
+ if (value === void 0) {
836
+ continue;
805
837
  }
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;
838
+ const existing = merged[key];
839
+ if (key === "className" && existing && value) {
840
+ merged[key] = `${existing} ${value}`;
841
+ continue;
839
842
  }
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"
843
+ if (key === "style" && existing && value) {
844
+ merged[key] = {
845
+ ...existing,
846
+ ...value
852
847
  };
853
- emit();
854
- }).catch((error) => {
855
- metaSnapshot = {
856
- ...metaSnapshot,
857
- error: error instanceof Error ? error : new Error(String(error)),
858
- status: "error"
848
+ continue;
849
+ }
850
+ if (key === "ref" && existing && value) {
851
+ merged[key] = composeRefs(existing, value);
852
+ continue;
853
+ }
854
+ if (isEventHandler(key) && typeof existing === "function" && typeof value === "function") {
855
+ merged[key] = (...args) => {
856
+ existing(...args);
857
+ const event = args[0];
858
+ if (!event?.defaultPrevented) {
859
+ value(...args);
860
+ }
859
861
  };
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;
862
+ continue;
863
+ }
864
+ merged[key] = value;
865
+ }
866
+ }
867
+ return merged;
868
+ }
869
+ function composeRefs(...refs) {
870
+ return (node) => {
871
+ for (const ref of refs) {
872
+ if (typeof ref === "function") {
873
+ ref(node);
874
+ } else if (ref) {
875
+ ref.current = node;
877
876
  }
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
877
  }
892
878
  };
893
879
  }
894
- function getScreenDepth(screen) {
895
- return screen === "/" ? 0 : screen.split("/").filter(Boolean).length;
880
+ function isEventHandler(key) {
881
+ return /^on[A-Z]/u.test(key);
896
882
  }
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)
883
+
884
+ // src/components/root.tsx
885
+ import { jsx as jsx4 } from "react/jsx-runtime";
886
+ function Root({ apps: appsProp, children, render, client, ...props }) {
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;
893
+ let store = previousStore;
894
+ if (previousStore.baseUrl.toString() !== baseUrl || previous.fetchClient !== client.fetchClient) {
895
+ store = createTailorKitStore(baseUrl, appsProp, client.fetchClient);
896
+ setStore({ store, fetchClient: client.fetchClient });
897
+ }
898
+ useEffect6(() => {
899
+ store.setProvidedApps(appsProp);
900
+ }, [store, appsProp]);
901
+ useEffect6(() => () => store.previews.dispose(), [store]);
902
+ const context = useMemo9(
903
+ () => ({
904
+ store,
905
+ client
906
+ }),
907
+ [store, client]
907
908
  );
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;
909
+ const element = render ? useRender({
910
+ defaultTagName: "div",
911
+ props: mergeProps({ children }, props),
912
+ render
913
+ }) : children;
914
+ return /* @__PURE__ */ jsx4(TailorRootContext.Provider, { value: context, children: element });
915
915
  }
916
916
  export {
917
+ Root,
918
+ Slot,
917
919
  components,
918
920
  createTailorKitClient,
919
921
  primitives