@tailorkit/react 0.1.0-beta.23 → 0.1.0-beta.25

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,326 +1,16 @@
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
-
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;
13
- }
14
-
15
- // src/hooks/use-views.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
-
19
- // src/components/context.ts
20
- import { createContext, useContext } from "react";
21
- var TailorRootContext = createContext(null);
22
- function useTailorRootContext(component, expectedClient) {
23
- const context = useContext(TailorRootContext);
24
- if (!context) {
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
- );
31
- }
32
- return context;
33
- }
34
-
35
- // src/hooks/use-apps.ts
36
- import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
37
- import { matchesApp } from "@tailorkit/client-core";
38
-
39
- // src/scope-query.ts
40
- import { appendScopeSelection, normalizeScopeSelection } from "@tailorkit/client-core";
41
-
42
- // src/hooks/use-apps.ts
43
- import { matchesApp as matchesApp2 } from "@tailorkit/client-core";
44
- function useApps(options = {}) {
45
- return useAppsStore(useTailorRootContext("useApps").store, options);
46
- }
47
- function useAppsStore(store, options = {}) {
48
- const snapshot = useAppsSnapshot(store);
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;
68
- }
69
- function toUseAppsResult(snapshot, refetch) {
70
- return {
71
- data: snapshot.status === "ready" ? snapshot.apps : void 0,
72
- error: snapshot.error,
73
- isFetching: snapshot.isFetching,
74
- isError: snapshot.status === "error",
75
- isLoading: snapshot.status === "loading",
76
- isPending: snapshot.status === "idle" || snapshot.status === "loading",
77
- isSuccess: snapshot.status === "ready",
78
- refetch,
79
- status: snapshot.status
80
- };
81
- }
82
-
83
- // src/hooks/use-views.ts
84
- function useViews({ slot, scopes, appIds }) {
85
- const { store } = useTailorRootContext("useViews");
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;
93
- }
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("useViews");
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
149
- };
150
- }
151
-
152
- // src/slot-view.ts
153
- import { resolveSlotView, selectSlotView } from "@tailorkit/client-core";
1
+ // src/components/render-slot.tsx
2
+ import { useStore as useStore2 } from "@nanostores/react";
3
+ import { createSlotRuntime, buildThemeCss } from "@tailorkit/client-core";
4
+ import { useEffect as useEffect2, useId as useId2, useMemo as useMemo3 } from "react";
154
5
 
155
6
  // src/primitives.tsx
156
- import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo3 } from "react";
157
- import { resolveTheme } from "@tailorkit/core/primitives/theme";
7
+ import { createContext, useContext, useId, useMemo } from "react";
8
+ import { buildPrimitiveCss } from "@tailorkit/client-core";
158
9
  import { jsx, jsxs } from "react/jsx-runtime";
159
- var PrimitiveThemeContext = createContext2(null);
160
- var tokenGroups = [
161
- "space",
162
- "radius",
163
- "background",
164
- "border",
165
- "borderColor",
166
- "textColor"
167
- ];
168
- var cssEscape = (value) => {
169
- const css = globalThis.CSS;
170
- if (typeof css?.escape === "function") {
171
- return css.escape(value);
172
- }
173
- return value.replaceAll(/[^A-Za-z0-9_-]/gu, "\\$&");
174
- };
175
- var toCssValue = (group, value) => {
176
- if (typeof value === "number") {
177
- return `${value}px`;
178
- }
179
- if (group === "size" || group === "raw") {
180
- return value;
181
- }
182
- return `var(--tailorkit-${group}-${cssEscape(value)})`;
183
- };
184
- var toResponsiveEntries = (value) => {
185
- if (value === void 0) {
186
- return [];
187
- }
188
- if (!(value && typeof value === "object") || Array.isArray(value)) {
189
- return [["base", value]];
190
- }
191
- return Object.entries(value).filter(
192
- (entry) => entry[1] !== void 0
193
- );
194
- };
195
- var declarationsForProp = (prop, value) => {
196
- if (prop === "columns") {
197
- return [`grid-template-columns: repeat(${String(value)}, minmax(0, 1fr));`];
198
- }
199
- const tokenDeclarations = {
200
- background: ["background", "background"],
201
- border: ["border", "border"],
202
- borderColor: ["borderColor", "border-color"],
203
- textColor: ["textColor", "color"]
204
- };
205
- const tokenDeclaration = tokenDeclarations[prop];
206
- if (tokenDeclaration) {
207
- const [group, property] = tokenDeclaration;
208
- if (prop === "border") {
209
- return [`border-style: ${toCssValue(group, value)};`, "border-width: 1px;"];
210
- }
211
- return [`${property}: ${toCssValue(group, value)};`];
212
- }
213
- if (prop === "gap" || prop === "margin" || prop === "padding") {
214
- return [`${prop}: ${toCssValue("space", value)};`];
215
- }
216
- if (prop === "height" || prop === "width") {
217
- return [`${prop}: ${toCssValue("size", value)};`];
218
- }
219
- if (prop === "radius") {
220
- return [`border-radius: ${toCssValue("radius", value)};`];
221
- }
222
- if (prop === "align") {
223
- return [`align-items: ${toFlexAlignment(value)};`];
224
- }
225
- if (prop === "justify") {
226
- return [`justify-content: ${toFlexJustification(value)};`];
227
- }
228
- if (prop === "direction") {
229
- return [`flex-direction: ${toCssValue("raw", value)};`];
230
- }
231
- if (prop === "overflow") {
232
- return [`overflow: ${toCssValue("raw", value)};`];
233
- }
234
- if (prop === "grow") {
235
- return [`flex-grow: ${String(value)};`];
236
- }
237
- if (prop === "shrink") {
238
- return [`flex-shrink: ${String(value)};`];
239
- }
240
- if (prop === "minHeight") {
241
- return [`min-height: ${toCssValue("size", value)};`];
242
- }
243
- if (prop === "minWidth") {
244
- return [`min-width: ${toCssValue("size", value)};`];
245
- }
246
- if (prop === "basis") {
247
- return [`flex-basis: ${toCssValue("size", value)};`];
248
- }
249
- if (prop === "wrap") {
250
- return [`flex-wrap: ${toCssValue("raw", value)};`];
251
- }
252
- return [];
253
- };
254
- var toFlexAlignment = (value) => {
255
- if (value === "start") {
256
- return "flex-start";
257
- }
258
- if (value === "end") {
259
- return "flex-end";
260
- }
261
- return String(value);
262
- };
263
- var toFlexJustification = (value) => {
264
- if (value === "start") {
265
- return "flex-start";
266
- }
267
- if (value === "end") {
268
- return "flex-end";
269
- }
270
- if (value === "between") {
271
- return "space-between";
272
- }
273
- return String(value);
274
- };
275
- var buildPrimitiveCss = ({
276
- display,
277
- nodeId,
278
- props,
279
- viewId,
280
- theme
281
- }) => {
282
- const selector = `[data-tailorkit-view="${cssEscape(viewId)}"] [data-tailorkit-node="${cssEscape(
283
- nodeId
284
- )}"]`;
285
- const base = [`display: ${display};`];
286
- const byBreakpoint = /* @__PURE__ */ new Map();
287
- for (const [prop, rawValue] of Object.entries(props)) {
288
- for (const [breakpoint, value] of toResponsiveEntries(rawValue)) {
289
- const declarations = declarationsForProp(prop, value);
290
- if (declarations.length === 0) {
291
- continue;
292
- }
293
- const target = breakpoint === "base" ? base : byBreakpoint.get(breakpoint) ?? [];
294
- target.push(...declarations);
295
- if (breakpoint !== "base") {
296
- byBreakpoint.set(breakpoint, target);
297
- }
298
- }
299
- }
300
- const rules = [`${selector} { ${base.join(" ")} }`];
301
- for (const [breakpoint, declarations] of byBreakpoint) {
302
- const minWidth = theme.breakpoints?.[breakpoint];
303
- if (!minWidth) {
304
- continue;
305
- }
306
- rules.push(`@media (min-width: ${minWidth}) { ${selector} { ${declarations.join(" ")} } }`);
307
- }
308
- return rules.join("\n");
309
- };
310
- var buildThemeCss = (viewId, theme) => {
311
- const selector = `[data-tailorkit-view="${cssEscape(viewId)}"]`;
312
- const declarations = [];
313
- const resolvedTheme = resolveTheme(theme);
314
- for (const group of tokenGroups) {
315
- for (const [name, value] of Object.entries(resolvedTheme.tokens?.[group] ?? {})) {
316
- declarations.push(`--tailorkit-${group}-${cssEscape(name)}: ${value};`);
317
- }
318
- }
319
- return `${selector} { ${declarations.join(" ")} }`;
320
- };
10
+ var PrimitiveThemeContext = createContext(null);
321
11
  var usePrimitiveNodeId = () => {
322
12
  const id = useId();
323
- return useMemo3(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
13
+ return useMemo(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
324
14
  };
325
15
  var Primitive = ({
326
16
  children,
@@ -328,7 +18,7 @@ var Primitive = ({
328
18
  element,
329
19
  props
330
20
  }) => {
331
- const context = useContext2(PrimitiveThemeContext);
21
+ const context = useContext(PrimitiveThemeContext);
332
22
  const nodeId = usePrimitiveNodeId();
333
23
  const Element = element;
334
24
  const css = context ? buildPrimitiveCss({
@@ -351,28 +41,20 @@ var primitives = {
351
41
  };
352
42
 
353
43
  // src/remote-view.tsx
44
+ import { useStore } from "@nanostores/react";
354
45
  import {
355
46
  Component,
356
47
  Fragment,
357
48
  createElement,
49
+ createContext as createContext2,
358
50
  memo,
359
- useCallback as useCallback3,
360
- useContext as useContext3,
361
- useEffect as useEffect3,
362
- useMemo as useMemo4,
363
- useRef as useRef2,
364
- useState,
365
- useSyncExternalStore as useSyncExternalStore3
51
+ useContext as useContext2,
52
+ useEffect,
53
+ useMemo as useMemo2
366
54
  } from "react";
367
- import { createIframeUiHost } from "@tailorkit/sandbox/host";
368
- import { NodeStore } from "@tailorkit/client-core";
369
-
370
- // src/remote-context.ts
371
- import { createContext as createContext3 } from "react";
372
- var RemoteUIContext = createContext3(null);
373
-
374
- // src/remote-view.tsx
55
+ import { createRemoteViewStore, createRemoteElementProps } from "@tailorkit/client-core";
375
56
  import { jsx as jsx2 } from "react/jsx-runtime";
57
+ var RemoteUIContext = createContext2(null);
376
58
  var formatError = (error) => error.message;
377
59
  var RemoteErrorBoundary = class extends Component {
378
60
  state = { error: null };
@@ -398,124 +80,44 @@ function RemoteViewHost({
398
80
  props
399
81
  }) {
400
82
  const appKey = appUrl.toString();
401
- const storeRef = useRef2(null);
402
- if (storeRef.current === null) {
403
- storeRef.current = new NodeStore();
404
- }
405
- const store = storeRef.current;
406
- const appKeyRef = useRef2(appKey);
407
- if (appKeyRef.current !== appKey) {
408
- appKeyRef.current = appKey;
409
- store.clear();
410
- }
411
- const hostRef = useRef2(null);
412
- const propsRef = useRef2(props);
413
- useEffect3(() => {
414
- propsRef.current = props;
415
- hostRef.current?.setProps(props);
416
- }, [props]);
417
- const dispatchRef = useRef2(null);
418
- const [error, setError] = useState(null);
419
- const [status, setStatus] = useState("starting");
420
- const ctxRef = useRef2({
421
- components: components2,
422
- dispatch: (payload) => dispatchRef.current?.(payload),
423
- store
424
- });
425
- useEffect3(() => {
426
- ctxRef.current.components = components2;
427
- }, [components2]);
428
- const ui = useMemo4(() => {
429
- const ctx = ctxRef.current;
430
- return createElement(
431
- RemoteUIContext.Provider,
432
- { value: ctx },
433
- createElement(
434
- RemoteErrorBoundary,
435
- { key: appKey },
436
- createElement(RemoteRoot, { store: ctx.store })
437
- )
438
- );
439
- }, [appKey]);
440
- useEffect3(() => {
441
- const host = createIframeUiHost(appUrl, {
442
- createIframe,
443
- sourceText,
444
- getBackendSession,
445
- onError: (error2) => {
446
- console.error("TailorKit remote app failed", error2);
447
- setError(error2);
448
- setStatus("error");
449
- },
450
- props: propsRef.current
451
- });
452
- hostRef.current = host;
453
- dispatchRef.current = (payload) => host.dispatch(payload);
454
- const unsubscribe = host.subscribe(() => {
455
- const tree = host.getSnapshot();
456
- if (tree !== null) {
457
- store.setSnapshot(tree);
458
- setStatus("ready");
459
- }
460
- });
461
- setStatus("starting");
462
- setError(null);
463
- host.mount();
464
- return () => {
465
- unsubscribe();
466
- dispatchRef.current = null;
467
- hostRef.current = null;
468
- host.destroy();
469
- };
470
- }, [appUrl, createIframe, sourceText, getBackendSession, store]);
471
- if (status === "error" && error) {
472
- return createElement("div", null, formatError(error));
83
+ const store = useMemo2(() => createRemoteViewStore({ components: components2, props }), [appKey]);
84
+ const snapshot = useStore(store.state);
85
+ useEffect(() => {
86
+ store.setComponents(components2);
87
+ }, [components2, store]);
88
+ useEffect(() => {
89
+ store.setProps(props);
90
+ }, [props, store]);
91
+ useEffect(
92
+ () => store.mount({ appUrl, sourceText, getBackendSession, createIframe }),
93
+ [appUrl, sourceText, getBackendSession, createIframe, store]
94
+ );
95
+ if (snapshot.status === "error" && snapshot.error) {
96
+ return createElement("div", null, formatError(snapshot.error));
473
97
  }
474
- return ui;
98
+ return /* @__PURE__ */ jsx2(RemoteUIContext.Provider, { value: store, children: /* @__PURE__ */ jsx2(RemoteErrorBoundary, { children: /* @__PURE__ */ jsx2(RemoteRoot, { store }) }, `${appKey}:${snapshot.generation}`) });
475
99
  }
476
100
  function RemoteRoot({ store }) {
477
- const subscribe = useCallback3((listener) => store.subscribeRoot(listener), [store]);
478
- const getSnapshot = useCallback3(() => store.getRootId(), [store]);
479
- const rootId = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
480
- if (rootId === null) {
481
- return null;
482
- }
483
- return /* @__PURE__ */ jsx2(RemoteView, { nodeId: rootId });
101
+ const rootId = useStore(store.nodes.root);
102
+ return rootId === null ? null : /* @__PURE__ */ jsx2(RemoteView, { nodeId: rootId });
484
103
  }
485
104
  var RemoteElementView = memo(({ ctx, node }) => {
486
- const { dispatch, components: components2 } = ctx;
105
+ const componentStore = useMemo2(() => ctx.selectComponent(node.type), [ctx, node.type]);
106
+ const component = useStore(componentStore);
487
107
  const children = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
488
- const component = components2[node.type];
489
108
  if (component === void 0) {
490
109
  throw new Error(`TailorKit component "${node.type}" is not registered.`);
491
110
  }
492
- const props = { ...node.props };
493
- for (const binding of node.callbacks ?? []) {
494
- props[binding.callback] = (...args) => {
495
- dispatch({
496
- data: {
497
- args: args.slice(0, binding.inputCount),
498
- event: binding.event,
499
- nodeId: node.id
500
- },
501
- type: "dispatchCallback"
502
- });
503
- };
504
- }
111
+ const props = createRemoteElementProps(node, ctx.dispatch);
505
112
  return createElement(component, props, ...children);
506
113
  });
507
114
  function RemoteViewComponent({ nodeId }) {
508
- const ctx = useContext3(RemoteUIContext);
509
- if (ctx === null) {
510
- return null;
511
- }
512
- const { store } = ctx;
513
- const subscribe = useCallback3(
514
- (listener) => store.subscribe(nodeId, listener),
515
- [store, nodeId]
516
- );
517
- const getSnapshot = useCallback3(() => store.getNode(nodeId), [store, nodeId]);
518
- const node = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
115
+ const ctx = useContext2(RemoteUIContext);
116
+ return ctx === null ? null : /* @__PURE__ */ jsx2(RemoteNodeView, { ctx, nodeId });
117
+ }
118
+ function RemoteNodeView({ ctx, nodeId }) {
119
+ const nodeStore = useMemo2(() => ctx.nodes.selectNode(nodeId), [ctx, nodeId]);
120
+ const node = useStore(nodeStore);
519
121
  if (node === null) {
520
122
  return null;
521
123
  }
@@ -530,210 +132,128 @@ function RemoteViewComponent({ nodeId }) {
530
132
  }
531
133
  var RemoteView = memo(RemoteViewComponent);
532
134
 
533
- // src/components/slot.tsx
534
- import { toBaseUrl } from "@tailorkit/client-core";
135
+ // src/components/context.ts
136
+ import { createContext as createContext3, useContext as useContext3 } from "react";
137
+ var TailorkitContext = createContext3(null);
138
+ function useTailorkitContext(component, expectedClient) {
139
+ const context = useContext3(TailorkitContext);
140
+ if (!context) {
141
+ throw new Error(`${component} must be rendered inside a TailorKit Provider.`);
142
+ }
143
+ if (expectedClient && context.client !== expectedClient) {
144
+ throw new Error(
145
+ `${component} was created for a different TailorKit client than the surrounding Provider.`
146
+ );
147
+ }
148
+ return context;
149
+ }
150
+
151
+ // src/components/render-slot.tsx
535
152
  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 });
153
+ function ManagedRenderSlot(props) {
154
+ return /* @__PURE__ */ jsx3(SlotRenderer, { mode: "managed", ...props });
545
155
  }
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 });
156
+ function ControlledRenderSlot(props) {
157
+ return /* @__PURE__ */ jsx3(SlotRenderer, { mode: "controlled", ...props });
589
158
  }
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]
159
+ var RenderSlot = Object.assign(ManagedRenderSlot, {
160
+ Controlled: ControlledRenderSlot
161
+ });
162
+ function SlotRenderer(options) {
163
+ const { store, client } = useTailorkitContext(
164
+ options.mode === "managed" ? "RenderSlot" : "RenderSlot.Controlled"
610
165
  );
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
166
  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
- }
167
+ const runtime = useMemo3(() => createSlotRuntime(store, options), [store]);
168
+ useEffect2(() => runtime.setInput(options), [runtime, options]);
169
+ const snapshot = useStore2(runtime.state);
170
+ if (snapshot.status === "hidden") return null;
171
+ if (snapshot.status === "loading") return /* @__PURE__ */ jsx3("div", { role: "status", children: snapshot.message });
172
+ if (snapshot.status === "error") return /* @__PURE__ */ jsx3("div", { role: "alert", children: snapshot.error.message });
689
173
  const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
690
174
  return /* @__PURE__ */ jsx3(PrimitiveThemeContext.Provider, { value: { viewId, theme: client.theme }, children: /* @__PURE__ */ jsxs2("div", { "data-tailorkit-view": viewId, children: [
691
175
  /* @__PURE__ */ jsx3("style", { "data-tailorkit-theme-style": viewId, children: buildThemeCss(viewId, client.theme) }),
692
176
  /* @__PURE__ */ jsx3(
693
177
  RemoteViewHost,
694
178
  {
695
- appUrl: (appUrl ?? new URL(`preview/${previewSessionId}/client.js`, store.baseUrl)).toString(),
696
- sourceText: preview.source ?? void 0,
697
- getBackendSession,
179
+ appUrl: snapshot.appUrl,
180
+ sourceText: snapshot.sourceText,
181
+ getBackendSession: snapshot.getBackendSession,
698
182
  components: client.components,
699
- props
183
+ props: snapshot.props
700
184
  },
701
- preview.revision || appUrl?.toString()
185
+ snapshot.hostKey
702
186
  )
703
187
  ] }) });
704
188
  }
705
189
 
706
190
  // src/tailorkit.tsx
707
- import { createTailorKitFetchClient } from "@tailorkit/client-core";
708
- import { createElement as createElement2 } from "react";
191
+ import {
192
+ createTailorKitClientConfig,
193
+ createComponentRegistry,
194
+ createTailorKitStore
195
+ } from "@tailorkit/client-core";
196
+ import { createElement as createElement2, useEffect as useEffect4, useMemo as useMemo8, useState } from "react";
709
197
 
710
198
  // src/hooks/use-view-context.ts
711
- import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
199
+ import { useEffect as useEffect3, useMemo as useMemo4 } from "react";
712
200
  function useViewContext(view, options) {
713
- const { store } = useTailorRootContext("useViewContext");
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(
201
+ const { store } = useTailorkitContext("useViewContext");
202
+ const id = useMemo4(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
203
+ const { context, loading, error } = options;
204
+ useEffect3(
719
205
  () => () => {
720
206
  store.views.unregister(id);
721
207
  },
722
208
  [id, store]
723
209
  );
724
- useEffect5(() => {
725
- store.views.register({
726
- context: contextSnapshot,
727
- id,
728
- view,
729
- status
730
- });
731
- }, [contextSnapshot, id, view, status, store]);
210
+ useEffect3(() => {
211
+ store.views.register({ id, view, context, loading, error });
212
+ }, [id, view, context, loading, error, store]);
213
+ }
214
+
215
+ // src/hooks/use-apps.ts
216
+ import { useMemo as useMemo6 } from "react";
217
+ import { appsQueryKey, createAppsQuery } from "@tailorkit/client-core";
218
+
219
+ // src/hooks/use-query-result.ts
220
+ import { useStore as useStore3 } from "@nanostores/react";
221
+ import { useMemo as useMemo5 } from "react";
222
+ function useQueryResult(state) {
223
+ const result = useStore3(state);
224
+ return useMemo5(
225
+ () => ({
226
+ data: result.data,
227
+ isPending: result.isPending,
228
+ error: result.error,
229
+ isRefetching: result.isFetching && !result.isPending,
230
+ fetch: result.refetch
231
+ }),
232
+ [result]
233
+ );
234
+ }
235
+
236
+ // src/hooks/use-apps.ts
237
+ function useApps(options = {}) {
238
+ return useAppsStore(useTailorkitContext("useApps").store, options);
239
+ }
240
+ function useAppsStore(store, options = {}) {
241
+ const key = appsQueryKey(options);
242
+ const query = useMemo6(() => createAppsQuery(store, options), [store, key]);
243
+ return useQueryResult(query.state);
244
+ }
245
+
246
+ // src/hooks/use-views.ts
247
+ import { useMemo as useMemo7 } from "react";
248
+ import { appsQueryKey as appsQueryKey2, createViewsQuery } from "@tailorkit/client-core";
249
+ function useViews(options) {
250
+ const { store } = useTailorkitContext("useViews");
251
+ const key = appsQueryKey2(options);
252
+ const query = useMemo7(() => createViewsQuery(store, options), [store, key, options.slot]);
253
+ return useQueryResult(query.state);
732
254
  }
733
255
 
734
256
  // src/tailorkit.tsx
735
- var componentTagPrefix = "tailorkit-";
736
- var toComponentTagName = (name) => `${componentTagPrefix}${name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1-$2").replaceAll(/[\s_]+/gu, "-").toLowerCase()}`;
737
257
  function components(_schema, customComponents) {
738
258
  return customComponents;
739
259
  }
@@ -741,175 +261,63 @@ function createTailorKitClient(options) {
741
261
  return createReactTailorKitClient(options);
742
262
  }
743
263
  function createReactTailorKitClient(options) {
744
- const wrappedComponents = {};
745
- const theme = options.theme ?? {};
746
- for (const [name, renderer] of Object.entries(options.components ?? {})) {
747
- if (renderer) {
748
- const TailorKitComponent = function TailorKitComponent2({
749
- children,
750
- ...props
751
- }) {
752
- return renderer({
753
- props,
754
- children
755
- });
756
- };
757
- wrappedComponents[name] = TailorKitComponent;
758
- wrappedComponents[toComponentTagName(name)] = TailorKitComponent;
759
- }
760
- }
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
770
- };
264
+ const wrappedComponents = createComponentRegistry(options.components ?? {}, (renderer) => {
265
+ return function TailorKitComponent({
266
+ children,
267
+ ...props
268
+ }) {
269
+ return renderer({
270
+ props,
271
+ children
272
+ });
273
+ };
274
+ });
275
+ const clientConfig = createTailorKitClientConfig({ ...options, components: wrappedComponents });
771
276
  const client = {
772
277
  ...clientConfig,
773
- Slot: Object.assign(
774
- function ClientSlot(props) {
775
- useTailorRootContext("Slot", client);
776
- return createElement2(Slot, props);
278
+ Provider: function TailorKitProvider({ children, apps }) {
279
+ const [store] = useState(
280
+ () => createTailorKitStore(client.baseUrl, apps, client.fetchClient)
281
+ );
282
+ useEffect4(() => {
283
+ store.setProvidedApps(apps);
284
+ }, [store, apps]);
285
+ useEffect4(() => () => store.previews.dispose(), [store]);
286
+ const context = useMemo8(() => ({ store, client }), [store]);
287
+ return createElement2(TailorkitContext.Provider, { value: context }, children);
288
+ },
289
+ RenderSlot: Object.assign(
290
+ function ClientRenderSlot(props) {
291
+ useTailorkitContext("RenderSlot", client);
292
+ return createElement2(RenderSlot, props);
777
293
  },
778
294
  {
779
- Controlled: function ClientControlledSlot(props) {
780
- useTailorRootContext("Slot.Controlled", client);
781
- return createElement2(Slot.Controlled, props);
295
+ Controlled: function ClientControlledRenderSlot(props) {
296
+ useTailorkitContext("RenderSlot.Controlled", client);
297
+ return createElement2(
298
+ RenderSlot.Controlled,
299
+ props
300
+ );
782
301
  }
783
302
  }
784
303
  ),
785
304
  useApps: function useClientApps(options2) {
786
- useTailorRootContext("useApps", client);
305
+ useTailorkitContext("useApps", client);
787
306
  return useApps(options2);
788
307
  },
789
308
  useViews: function useClientViews(options2) {
790
- useTailorRootContext("useViews", client);
309
+ useTailorkitContext("useViews", client);
791
310
  return useViews(options2);
792
311
  },
793
312
  useViewContext: function useClientViewContext(view, options2) {
794
- useTailorRootContext("useViewContext", client);
313
+ useTailorkitContext("useViewContext", client);
795
314
  useViewContext(view, options2);
796
315
  }
797
316
  };
798
317
  return client;
799
318
  }
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.");
816
- }
817
- return cloneElement(render, mergeProps(render.props, props));
818
- }
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;
827
- }
828
- for (const [key, value] of Object.entries(props)) {
829
- if (value === void 0) {
830
- continue;
831
- }
832
- const existing = merged[key];
833
- if (key === "className" && existing && value) {
834
- merged[key] = `${existing} ${value}`;
835
- continue;
836
- }
837
- if (key === "style" && existing && value) {
838
- merged[key] = {
839
- ...existing,
840
- ...value
841
- };
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
- }
855
- };
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;
870
- }
871
- }
872
- };
873
- }
874
- function isEventHandler(key) {
875
- return /^on[A-Z]/u.test(key);
876
- }
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]
902
- );
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 });
909
- }
910
319
  export {
911
- Root,
912
- Slot,
320
+ RenderSlot,
913
321
  components,
914
322
  createTailorKitClient,
915
323
  primitives