@replohq/sandbox-runtime 1.0.1 → 1.2.0

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.
@@ -42,6 +42,24 @@ function LinkInterceptor() {
42
42
  const originalPushState = history.pushState;
43
43
  const originalReplaceState = history.replaceState;
44
44
  const navigation = getWindowNavigation();
45
+ const reportNavigated = (rawUrl) => {
46
+ let route;
47
+ try {
48
+ route = new URL(
49
+ typeof rawUrl === "string" ? rawUrl : rawUrl.href,
50
+ window.location.href
51
+ ).pathname;
52
+ } catch {
53
+ route = String(rawUrl);
54
+ }
55
+ window.parent.postMessage(
56
+ {
57
+ type: "replo:navigated",
58
+ route
59
+ },
60
+ "*"
61
+ );
62
+ };
45
63
  window.open = function(url, target, features) {
46
64
  if (url !== void 0 && url !== "" && urlLeavesSandbox(url) === true) {
47
65
  return null;
@@ -52,13 +70,21 @@ function LinkInterceptor() {
52
70
  if (url && urlLeavesSandbox(url) === true) {
53
71
  return;
54
72
  }
55
- return originalPushState.call(history, state, unused, url);
73
+ const result = originalPushState.call(history, state, unused, url);
74
+ if (url) {
75
+ reportNavigated(url);
76
+ }
77
+ return result;
56
78
  };
57
79
  history.replaceState = function(state, unused, url) {
58
80
  if (url && urlLeavesSandbox(url) === true) {
59
81
  return;
60
82
  }
61
- return originalReplaceState.call(history, state, unused, url);
83
+ const result = originalReplaceState.call(history, state, unused, url);
84
+ if (url) {
85
+ reportNavigated(url);
86
+ }
87
+ return result;
62
88
  };
63
89
  const handleNavigate = (event) => {
64
90
  const destinationUrl = event.destination?.url;
@@ -95,20 +121,18 @@ function LinkInterceptor() {
95
121
  blockNavigation(event);
96
122
  return;
97
123
  }
98
- event.preventDefault();
99
- let route;
100
- try {
101
- route = new URL(href, window.location.href).pathname;
102
- } catch {
103
- route = href;
124
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
125
+ return;
104
126
  }
105
- window.parent.postMessage(
106
- {
107
- type: "replo:navigate",
108
- route
109
- },
110
- "*"
111
- );
127
+ if (link.hasAttribute("download")) {
128
+ return;
129
+ }
130
+ queueMicrotask(() => {
131
+ if (event.defaultPrevented) {
132
+ return;
133
+ }
134
+ reportNavigated(href);
135
+ });
112
136
  };
113
137
  navigation?.addEventListener("navigate", handleNavigate);
114
138
  document.addEventListener("click", handleClick, true);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../LinkInterceptor.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport { useEffect } from \"react\";\n\nimport { displayModeStore } from \"./select-mode/displayModeStore\";\n\ntype NavigationNavigateEvent = {\n destination?: { url?: string };\n preventDefault?: () => void;\n};\n\ntype NavigationLike = {\n addEventListener: (\n type: \"navigate\",\n listener: (event: NavigationNavigateEvent) => void,\n ) => void;\n removeEventListener: (\n type: \"navigate\",\n listener: (event: NavigationNavigateEvent) => void,\n ) => void;\n};\n\nfunction blockNavigation(event: MouseEvent) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n}\n\nfunction isNavigationLike(value: unknown): value is NavigationLike {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n typeof Reflect.get(value, \"addEventListener\") === \"function\" &&\n typeof Reflect.get(value, \"removeEventListener\") === \"function\"\n );\n}\n\nfunction getWindowNavigation() {\n const navigation = Reflect.get(window, \"navigation\");\n return isNavigationLike(navigation) ? navigation : null;\n}\n\nfunction shouldInterceptNavigationForAgentPreview() {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n if (window.parent === window) {\n return false;\n }\n\n return (\n new URLSearchParams(window.location.search).get(\"__reploAgentPreview\") ===\n \"1\"\n );\n}\n\nfunction urlLeavesSandbox(rawUrl: string | URL): boolean | null {\n try {\n const resolved =\n typeof rawUrl === \"string\"\n ? new URL(rawUrl, window.location.href)\n : new URL(rawUrl.href, window.location.href);\n return resolved.origin !== window.location.origin;\n } catch {\n return null;\n }\n}\n\nexport default function LinkInterceptor() {\n // eslint-disable-next-line replo/no-use-effect -- Browser navigation APIs must be patched at client runtime and restored when the interceptor unmounts.\n useEffect(() => {\n if (!shouldInterceptNavigationForAgentPreview()) {\n return;\n }\n\n const originalWindowOpen = window.open;\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n const navigation = getWindowNavigation();\n\n window.open = function (\n url?: string | URL,\n target?: string,\n features?: string,\n ) {\n if (url !== undefined && url !== \"\" && urlLeavesSandbox(url) === true) {\n return null;\n }\n\n return originalWindowOpen.call(window, url, target, features);\n };\n\n history.pushState = function (\n state: Parameters<History[\"pushState\"]>[0],\n unused: string,\n url?: string | URL | null,\n ) {\n if (url && urlLeavesSandbox(url) === true) {\n return;\n }\n\n return originalPushState.call(history, state, unused, url);\n };\n\n history.replaceState = function (\n state: Parameters<History[\"replaceState\"]>[0],\n unused: string,\n url?: string | URL | null,\n ) {\n if (url && urlLeavesSandbox(url) === true) {\n return;\n }\n\n return originalReplaceState.call(history, state, unused, url);\n };\n\n const handleNavigate = (event: NavigationNavigateEvent) => {\n const destinationUrl = event.destination?.url;\n if (\n destinationUrl === undefined ||\n destinationUrl === \"\" ||\n urlLeavesSandbox(destinationUrl) !== true\n ) {\n return;\n }\n\n event.preventDefault?.();\n };\n\n const handleClick = (event: MouseEvent) => {\n // While select mode is active a link click must select the element, not\n // navigate. `useSelectionInteractions` prevents the default nav, but this\n // sibling capture listener would still post `replo:navigate` and move the\n // preview off the page \u2014 `stopPropagation` there can't suppress a\n // listener on the same node, so we bail out here explicitly.\n if (displayModeStore.getSnapshot() === \"selection\") {\n return;\n }\n\n const target = event.target;\n\n if (!(target instanceof Element)) {\n return;\n }\n\n const link = target.closest(\"a[href]\");\n\n if (!(link instanceof HTMLAnchorElement)) {\n return;\n }\n\n const href = link.getAttribute(\"href\");\n if (!href) {\n return;\n }\n\n if (href.startsWith(\"#\")) {\n return;\n }\n\n const linkTarget = link.getAttribute(\"target\");\n if (linkTarget && linkTarget !== \"_self\") {\n blockNavigation(event);\n return;\n }\n\n if (urlLeavesSandbox(href) === true) {\n blockNavigation(event);\n return;\n }\n\n event.preventDefault();\n\n let route: string;\n try {\n route = new URL(href, window.location.href).pathname;\n } catch {\n route = href;\n }\n\n window.parent.postMessage(\n {\n type: \"replo:navigate\",\n route,\n },\n \"*\",\n );\n };\n\n navigation?.addEventListener(\"navigate\", handleNavigate);\n document.addEventListener(\"click\", handleClick, true);\n\n return () => {\n window.open = originalWindowOpen;\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n navigation?.removeEventListener(\"navigate\", handleNavigate);\n document.removeEventListener(\"click\", handleClick, true);\n };\n }, []);\n\n return null;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\";\n\nimport { useEffect } from \"react\";\n\nimport { displayModeStore } from \"./select-mode/displayModeStore\";\n\ntype NavigationNavigateEvent = {\n destination?: { url?: string };\n preventDefault?: () => void;\n};\n\ntype NavigationLike = {\n addEventListener: (\n type: \"navigate\",\n listener: (event: NavigationNavigateEvent) => void,\n ) => void;\n removeEventListener: (\n type: \"navigate\",\n listener: (event: NavigationNavigateEvent) => void,\n ) => void;\n};\n\nfunction blockNavigation(event: MouseEvent) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n}\n\nfunction isNavigationLike(value: unknown): value is NavigationLike {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n typeof Reflect.get(value, \"addEventListener\") === \"function\" &&\n typeof Reflect.get(value, \"removeEventListener\") === \"function\"\n );\n}\n\nfunction getWindowNavigation() {\n const navigation = Reflect.get(window, \"navigation\");\n return isNavigationLike(navigation) ? navigation : null;\n}\n\nfunction shouldInterceptNavigationForAgentPreview() {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n if (window.parent === window) {\n return false;\n }\n\n return (\n new URLSearchParams(window.location.search).get(\"__reploAgentPreview\") ===\n \"1\"\n );\n}\n\nfunction urlLeavesSandbox(rawUrl: string | URL): boolean | null {\n try {\n const resolved =\n typeof rawUrl === \"string\"\n ? new URL(rawUrl, window.location.href)\n : new URL(rawUrl.href, window.location.href);\n return resolved.origin !== window.location.origin;\n } catch {\n return null;\n }\n}\n\nexport default function LinkInterceptor() {\n // eslint-disable-next-line replo/no-use-effect -- Browser navigation APIs must be patched at client runtime and restored when the interceptor unmounts.\n useEffect(() => {\n if (!shouldInterceptNavigationForAgentPreview()) {\n return;\n }\n\n const originalWindowOpen = window.open;\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n const navigation = getWindowNavigation();\n\n // Tell the parent editor where the site's own navigation went so it can\n // keep its URL, page list, and select-mode in sync without reloading the\n // iframe. A fact, not a request: it is only sent once a navigation is\n // actually happening.\n const reportNavigated = (rawUrl: string | URL) => {\n let route: string;\n try {\n route = new URL(\n typeof rawUrl === \"string\" ? rawUrl : rawUrl.href,\n window.location.href,\n ).pathname;\n } catch {\n route = String(rawUrl);\n }\n window.parent.postMessage(\n {\n type: \"replo:navigated\",\n route,\n },\n \"*\",\n );\n };\n\n window.open = function (\n url?: string | URL,\n target?: string,\n features?: string,\n ) {\n if (url !== undefined && url !== \"\" && urlLeavesSandbox(url) === true) {\n return null;\n }\n\n return originalWindowOpen.call(window, url, target, features);\n };\n\n // The history patches are where client-side navigations are reported:\n // next/link cancels its click and routes through pushState, so the click\n // handler below cannot know whether the site actually navigated \u2014 these\n // patches fire exactly when it did (link clicks and programmatic\n // router.push/redirects alike).\n history.pushState = function (\n state: Parameters<History[\"pushState\"]>[0],\n unused: string,\n url?: string | URL | null,\n ) {\n if (url && urlLeavesSandbox(url) === true) {\n return;\n }\n\n const result = originalPushState.call(history, state, unused, url);\n if (url) {\n reportNavigated(url);\n }\n return result;\n };\n\n history.replaceState = function (\n state: Parameters<History[\"replaceState\"]>[0],\n unused: string,\n url?: string | URL | null,\n ) {\n if (url && urlLeavesSandbox(url) === true) {\n return;\n }\n\n const result = originalReplaceState.call(history, state, unused, url);\n if (url) {\n reportNavigated(url);\n }\n return result;\n };\n\n const handleNavigate = (event: NavigationNavigateEvent) => {\n const destinationUrl = event.destination?.url;\n if (\n destinationUrl === undefined ||\n destinationUrl === \"\" ||\n urlLeavesSandbox(destinationUrl) !== true\n ) {\n return;\n }\n\n event.preventDefault?.();\n };\n\n const handleClick = (event: MouseEvent) => {\n // While select mode is active a link click must select the element, not\n // navigate. `useSelectionInteractions` prevents the default nav, but this\n // sibling capture listener would still post `replo:navigate` and move the\n // preview off the page \u2014 `stopPropagation` there can't suppress a\n // listener on the same node, so we bail out here explicitly.\n if (displayModeStore.getSnapshot() === \"selection\") {\n return;\n }\n\n const target = event.target;\n\n if (!(target instanceof Element)) {\n return;\n }\n\n const link = target.closest(\"a[href]\");\n\n if (!(link instanceof HTMLAnchorElement)) {\n return;\n }\n\n const href = link.getAttribute(\"href\");\n if (!href) {\n return;\n }\n\n if (href.startsWith(\"#\")) {\n return;\n }\n\n const linkTarget = link.getAttribute(\"target\");\n if (linkTarget && linkTarget !== \"_self\") {\n blockNavigation(event);\n return;\n }\n\n if (urlLeavesSandbox(href) === true) {\n blockNavigation(event);\n return;\n }\n\n // A modified click (new tab / etc.) is the browser's to handle \u2014 the\n // iframe sandbox has no `allow-popups`, so it can't escape anyway.\n // Don't hijack it or tell the parent we navigated.\n if (\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n\n // A download link never navigates, so there is nothing to report.\n if (link.hasAttribute(\"download\")) {\n return;\n }\n\n // Let the site navigate itself \u2014 next/link does an instant client-side\n // transition, exactly like a normal Next.js app \u2014 instead of cancelling\n // the click and making the parent editor tear down and reload the whole\n // preview iframe. next/link navigations are reported by the pushState\n // patch above; this deferred report covers plain anchors the browser\n // itself navigates (a full document load inside the iframe). Deferred a\n // microtask because this capture listener runs before the site's own\n // handlers: a cancelled click (next/link included) navigates nothing.\n queueMicrotask(() => {\n if (event.defaultPrevented) {\n return;\n }\n reportNavigated(href);\n });\n };\n\n navigation?.addEventListener(\"navigate\", handleNavigate);\n document.addEventListener(\"click\", handleClick, true);\n\n return () => {\n window.open = originalWindowOpen;\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n navigation?.removeEventListener(\"navigate\", handleNavigate);\n document.removeEventListener(\"click\", handleClick, true);\n };\n }, []);\n\n return null;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,14 +3,22 @@ export declare const MAX_COMPONENT_PREVIEW_PROPS_BYTES: number;
3
3
  export declare const componentPreviewPropNameSchema: z.ZodString;
4
4
  export declare const componentPreviewPropsSchema: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
5
5
  export declare const COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID: "replo:component-preview-iframe-rpc";
6
- export declare const COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION: 1;
6
+ export declare const COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION: 2;
7
+ export declare const COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM = "reploParentOrigin";
7
8
  export declare const websiteBuilderToComponentPreviewMethodConfig: {
8
9
  setProps: {
9
10
  inputSchema: z.ZodObject<{
10
11
  props: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
11
12
  sourceRevision: z.ZodString;
12
13
  }, z.core.$strip>;
13
- outputSchema: z.ZodVoid;
14
+ outputSchema: z.ZodObject<{
15
+ mode: z.ZodEnum<{
16
+ client: "client";
17
+ navigation: "navigation";
18
+ unchanged: "unchanged";
19
+ }>;
20
+ }, z.core.$strip>;
21
+ timeoutMs: number;
14
22
  };
15
23
  };
16
24
  export declare const componentPreviewToWebsiteBuilderMethodConfig: {
@@ -32,16 +32,25 @@ var componentPreviewPropNameSchema = z.string().min(1).max(80).regex(/^[A-Za-z_$
32
32
  name
33
33
  );
34
34
  }, "Unsafe prop name");
35
- var componentPreviewPropsSchema = z.record(z.string(), z.json());
35
+ var componentPreviewPropsSchema = z.record(
36
+ componentPreviewPropNameSchema,
37
+ z.json()
38
+ );
36
39
  var COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID = "replo:component-preview-iframe-rpc";
37
- var COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION = 1;
40
+ var COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION = 2;
41
+ var COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM = "reploParentOrigin";
38
42
  var websiteBuilderToComponentPreviewMethodConfig = {
39
43
  setProps: {
40
44
  inputSchema: z.object({
41
45
  props: componentPreviewPropsSchema,
42
46
  sourceRevision: z.string().min(1)
43
47
  }),
44
- outputSchema: z.void()
48
+ // How the frame applied the update: in place via the client prop store,
49
+ // via an in-frame server navigation, or not at all (already current).
50
+ outputSchema: z.object({
51
+ mode: z.enum(["client", "navigation", "unchanged"])
52
+ }),
53
+ timeoutMs: 1e4
45
54
  }
46
55
  };
47
56
  var componentPreviewToWebsiteBuilderMethodConfig = {
@@ -94,4 +103,4 @@ var componentPreviewPreparationSchema = z.discriminatedUnion("status", [
94
103
  z.object({ status: z.literal("siteUpdateRequired") })
95
104
  ]).describe("ComponentPreviewPreparation");
96
105
 
97
- export { COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID, COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION, MAX_COMPONENT_PREVIEW_PROPS_BYTES, componentPreviewComponentSchema, componentPreviewIndexSchema, componentPreviewPathSchema, componentPreviewPreparationSchema, componentPreviewProductReferenceSchema, componentPreviewPropNameSchema, componentPreviewPropsSchema, componentPreviewSelectionSchema, componentPreviewSourceFileSchema, componentPreviewSourceSchema, componentPreviewToWebsiteBuilderMethodConfig, websiteBuilderToComponentPreviewMethodConfig };
106
+ export { COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID, COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION, COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM, MAX_COMPONENT_PREVIEW_PROPS_BYTES, componentPreviewComponentSchema, componentPreviewIndexSchema, componentPreviewPathSchema, componentPreviewPreparationSchema, componentPreviewProductReferenceSchema, componentPreviewPropNameSchema, componentPreviewPropsSchema, componentPreviewSelectionSchema, componentPreviewSourceFileSchema, componentPreviewSourceSchema, componentPreviewToWebsiteBuilderMethodConfig, websiteBuilderToComponentPreviewMethodConfig };
@@ -1,6 +1,6 @@
1
1
  export type DisplayMode = "browse" | "selection";
2
2
  export type SelectModeSelectionType = "none" | "element" | "area" | "drag";
3
- export type SelectModeInlineTextStatus = "editable" | "dynamic" | null;
3
+ export type SelectModeInlineTextStatus = "editable" | "dynamic" | "translated" | null;
4
4
  export type SelectModeTextMetrics = {
5
5
  tightWidth: number;
6
6
  lineCount: number;
@@ -8,6 +8,7 @@ export declare const displayModeSchema: z.ZodEnum<{
8
8
  export declare const selectModeInlineTextStatusSchema: z.ZodNullable<z.ZodEnum<{
9
9
  editable: "editable";
10
10
  dynamic: "dynamic";
11
+ translated: "translated";
11
12
  }>>;
12
13
  export declare const selectModeTextMetricsSchema: z.ZodObject<{
13
14
  tightWidth: z.ZodNumber;
@@ -86,13 +87,15 @@ export type RectOffset = {
86
87
  left: number;
87
88
  top: number;
88
89
  };
89
- export declare function transformRect({ rect, offset, }: {
90
+ export declare function transformRect({ rect, offset, scale, }: {
90
91
  rect: SelectModeBoundingRect;
91
92
  offset: RectOffset;
93
+ scale?: number;
92
94
  }): SelectModeBoundingRect;
93
- export declare function transformPoint({ point, offset, }: {
95
+ export declare function transformPoint({ point, offset, scale, }: {
94
96
  point: SelectModeClickPosition;
95
97
  offset: RectOffset;
98
+ scale?: number;
96
99
  }): SelectModeClickPosition;
97
100
  export declare function getIframeOffset(iframe: HTMLIFrameElement | null): RectOffset;
98
101
  export declare const selectedElementSchema: z.ZodObject<{
@@ -256,6 +259,11 @@ export type SelectModeBoundingRect = z.infer<typeof selectModeBoundingRectSchema
256
259
  export type SelectModeClickPosition = z.infer<typeof selectModeClickPositionSchema>;
257
260
  export type SelectableData = z.infer<typeof selectableDataSchema>;
258
261
  export type SelectableDataPayload = z.infer<typeof selectableDataPayloadSchema>;
262
+ export declare function transformSelectedElement({ element, offset, scale, }: {
263
+ element: z.infer<typeof selectedElementSchema>;
264
+ offset: RectOffset;
265
+ scale?: number;
266
+ }): z.infer<typeof selectedElementSchema>;
259
267
  export declare function transformSelectableData({ data, offset, }: {
260
268
  data: SelectableData;
261
269
  offset: RectOffset;
@@ -1,3 +1,3 @@
1
- export { SELECT_MODE_SELECTABLE_ATTRIBUTE, SELECT_MODE_SELECTABLE_PAYLOAD_ATTRIBUTE, displayModeSchema, getIframeOffset, readSelectablePayload, selectModeBoundingRectSchema, selectModeClickPositionSchema, selectModeDefinedStyleSchema, selectModeFiberChainEntrySchema, selectModeFormatCommandSchema, selectModeInlineTextStatusSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionRectSchema, selectModeSelectionSchema, selectModeSelectionTypeSchema, selectModeSourceLocationSchema, selectModeStyleAtRuleSchema, selectModeStyleVariantSchema, selectModeTextMetricsSchema, selectModeTiptapActiveMarksSchema, selectableDataPayloadSchema, selectableDataSchema, selectablePlanDataSchema, selectablePlanPayloadSchema, selectedElementSchema, transformPoint, transformRect, transformSelectableData } from '../../chunk-3UFG7XTQ.mjs';
1
+ export { SELECT_MODE_SELECTABLE_ATTRIBUTE, SELECT_MODE_SELECTABLE_PAYLOAD_ATTRIBUTE, displayModeSchema, getIframeOffset, readSelectablePayload, selectModeBoundingRectSchema, selectModeClickPositionSchema, selectModeDefinedStyleSchema, selectModeFiberChainEntrySchema, selectModeFormatCommandSchema, selectModeInlineTextStatusSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionRectSchema, selectModeSelectionSchema, selectModeSelectionTypeSchema, selectModeSourceLocationSchema, selectModeStyleAtRuleSchema, selectModeStyleVariantSchema, selectModeTextMetricsSchema, selectModeTiptapActiveMarksSchema, selectableDataPayloadSchema, selectableDataSchema, selectablePlanDataSchema, selectablePlanPayloadSchema, selectedElementSchema, transformPoint, transformRect, transformSelectableData, transformSelectedElement } from '../../chunk-2455EG5R.mjs';
2
2
  import '../../chunk-N5EI2MOZ.mjs';
3
3
  import '../../chunk-FNTGY7I5.mjs';
@@ -1,4 +1,4 @@
1
- import { selectModeFormatCommandSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionTypeSchema, displayModeSchema, selectModeTiptapActiveMarksSchema, selectModeBoundingRectSchema, selectableDataSchema, selectModeSelectionRectSchema, selectModeClickPositionSchema, selectedElementSchema } from '../../chunk-3UFG7XTQ.mjs';
1
+ import { selectModeFormatCommandSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionTypeSchema, displayModeSchema, selectModeTiptapActiveMarksSchema, selectModeBoundingRectSchema, selectableDataSchema, selectModeSelectionRectSchema, selectModeClickPositionSchema, selectedElementSchema } from '../../chunk-2455EG5R.mjs';
2
2
  import '../../chunk-N5EI2MOZ.mjs';
3
3
  import '../../chunk-FNTGY7I5.mjs';
4
4
  import { z } from 'zod';
package/buildMetadata.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sandbox-runtime",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "branch": "HEAD",
5
- "commit": "9fc57a3ef1d06689e70fe0d90c25c03717e37d03",
6
- "builtAt": "2026-08-19T22:39:33.469Z"
5
+ "commit": "0460cd76d21b76aeefd6d972a25dab1b917d34d0",
6
+ "builtAt": "2026-08-25T19:38:53.050Z"
7
7
  };
@@ -18,7 +18,7 @@ function parseSchemaOrNull(value, schema) {
18
18
  var SELECT_MODE_SELECTABLE_ATTRIBUTE = "data-replo-selectable";
19
19
  var SELECT_MODE_SELECTABLE_PAYLOAD_ATTRIBUTE = "data-replo-selectable-payload";
20
20
  var displayModeSchema = z.enum(["browse", "selection"]).describe("DisplayMode");
21
- var selectModeInlineTextStatusSchema = z.enum(["editable", "dynamic"]).nullable().describe("SelectModeInlineTextStatus");
21
+ var selectModeInlineTextStatusSchema = z.enum(["editable", "dynamic", "translated"]).nullable().describe("SelectModeInlineTextStatus");
22
22
  var selectModeTextMetricsSchema = z.object({
23
23
  tightWidth: z.number(),
24
24
  lineCount: z.number(),
@@ -71,26 +71,28 @@ var selectModeDefinedStyleSchema = z.object({
71
71
  }).describe("SelectModeDefinedStyle");
72
72
  function transformRect({
73
73
  rect,
74
- offset
74
+ offset,
75
+ scale = 1
75
76
  }) {
76
77
  return {
77
- x: rect.x + offset.left,
78
- y: rect.y + offset.top,
79
- left: rect.left + offset.left,
80
- right: rect.right + offset.left,
81
- top: rect.top + offset.top,
82
- bottom: rect.bottom + offset.top,
83
- width: rect.width,
84
- height: rect.height
78
+ x: rect.x * scale + offset.left,
79
+ y: rect.y * scale + offset.top,
80
+ left: rect.left * scale + offset.left,
81
+ right: rect.right * scale + offset.left,
82
+ top: rect.top * scale + offset.top,
83
+ bottom: rect.bottom * scale + offset.top,
84
+ width: rect.width * scale,
85
+ height: rect.height * scale
85
86
  };
86
87
  }
87
88
  function transformPoint({
88
89
  point,
89
- offset
90
+ offset,
91
+ scale = 1
90
92
  }) {
91
93
  return {
92
- x: point.x + offset.left,
93
- y: point.y + offset.top
94
+ x: point.x * scale + offset.left,
95
+ y: point.y * scale + offset.top
94
96
  };
95
97
  }
96
98
  function getIframeOffset(iframe) {
@@ -125,6 +127,21 @@ var selectablePlanDataSchema = z.object({
125
127
  var selectablePlanPayloadSchema = selectablePlanDataSchema.omit({ rect: true }).describe("SelectablePlanPayload");
126
128
  var selectableDataPayloadSchema = z.discriminatedUnion("kind", [selectablePlanPayloadSchema]).describe("SelectableDataPayload");
127
129
  var selectableDataSchema = z.discriminatedUnion("kind", [selectedElementSchema, selectablePlanDataSchema]).describe("SelectableData");
130
+ function transformSelectedElement({
131
+ element,
132
+ offset,
133
+ scale = 1
134
+ }) {
135
+ return {
136
+ ...element,
137
+ rect: transformRect({ rect: element.rect, offset, scale }),
138
+ clickPosition: transformPoint({
139
+ point: element.clickPosition,
140
+ offset,
141
+ scale
142
+ })
143
+ };
144
+ }
128
145
  function transformSelectableData({
129
146
  data,
130
147
  offset
@@ -133,14 +150,7 @@ function transformSelectableData({
133
150
  data,
134
151
  "kind"
135
152
  )({
136
- "react-element": (element) => ({
137
- ...element,
138
- rect: transformRect({ rect: element.rect, offset }),
139
- clickPosition: transformPoint({
140
- point: element.clickPosition,
141
- offset
142
- })
143
- }),
153
+ "react-element": (element) => transformSelectedElement({ element, offset }),
144
154
  plan: (plan) => ({
145
155
  ...plan,
146
156
  rect: transformRect({ rect: plan.rect, offset })
@@ -202,4 +212,4 @@ var selectModeSelectionSchema = z.discriminatedUnion("type", [
202
212
  })
203
213
  ]).describe("SelectModeSelection");
204
214
 
205
- export { SELECT_MODE_SELECTABLE_ATTRIBUTE, SELECT_MODE_SELECTABLE_PAYLOAD_ATTRIBUTE, displayModeSchema, getIframeOffset, readSelectablePayload, selectModeBoundingRectSchema, selectModeClickPositionSchema, selectModeDefinedStyleSchema, selectModeFiberChainEntrySchema, selectModeFormatCommandSchema, selectModeInlineTextStatusSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionRectSchema, selectModeSelectionSchema, selectModeSelectionTypeSchema, selectModeSourceLocationSchema, selectModeStyleAtRuleSchema, selectModeStyleVariantSchema, selectModeTextMetricsSchema, selectModeTiptapActiveMarksSchema, selectableDataPayloadSchema, selectableDataSchema, selectablePlanDataSchema, selectablePlanPayloadSchema, selectedElementSchema, transformPoint, transformRect, transformSelectableData };
215
+ export { SELECT_MODE_SELECTABLE_ATTRIBUTE, SELECT_MODE_SELECTABLE_PAYLOAD_ATTRIBUTE, displayModeSchema, getIframeOffset, readSelectablePayload, selectModeBoundingRectSchema, selectModeClickPositionSchema, selectModeDefinedStyleSchema, selectModeFiberChainEntrySchema, selectModeFormatCommandSchema, selectModeInlineTextStatusSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionRectSchema, selectModeSelectionSchema, selectModeSelectionTypeSchema, selectModeSourceLocationSchema, selectModeStyleAtRuleSchema, selectModeStyleVariantSchema, selectModeTextMetricsSchema, selectModeTiptapActiveMarksSchema, selectableDataPayloadSchema, selectableDataSchema, selectablePlanDataSchema, selectablePlanPayloadSchema, selectedElementSchema, transformPoint, transformRect, transformSelectableData, transformSelectedElement };
@@ -0,0 +1,8 @@
1
+ import type { ComponentType } from "react";
2
+ export declare function ComponentPreviewNavigation(): null;
3
+ export declare function ComponentPreviewStage({ Component, initialProps, initialRawProps, initialSourceRevision, }: {
4
+ Component: ComponentType<Record<string, unknown>>;
5
+ initialProps: Record<string, unknown>;
6
+ initialRawProps: Record<string, unknown>;
7
+ initialSourceRevision: string;
8
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,174 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useRef, useState, useTransition } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useIframeRpcChannel } from "../_vendor/replo-utils/react/use-iframe-rpc-channel.mjs";
6
+ import {
7
+ COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,
8
+ COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,
9
+ COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM,
10
+ componentPreviewToWebsiteBuilderMethodConfig,
11
+ websiteBuilderToComponentPreviewMethodConfig
12
+ } from "../_vendor/schemas/componentPreview.mjs";
13
+ import {
14
+ createIframeRpcChannel,
15
+ createIframeRpcRemote
16
+ } from "../_vendor/schemas/iframeRpcChannel.mjs";
17
+ import { getComponentPreviewHref } from "./component-preview-href";
18
+ import {
19
+ mergeClientProps,
20
+ productReferencesChanged
21
+ } from "./component-preview-props";
22
+ function getParentOrigin() {
23
+ const fromQuery = new URLSearchParams(window.location.search).get(
24
+ COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM
25
+ );
26
+ if (fromQuery) {
27
+ try {
28
+ return new URL(fromQuery).origin;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ return document.referrer ? new URL(document.referrer).origin : null;
34
+ }
35
+ function getCurrentHref() {
36
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
37
+ }
38
+ function useComponentPreviewChannel(onSetProps) {
39
+ const onSetPropsRef = useRef(onSetProps);
40
+ onSetPropsRef.current = onSetProps;
41
+ useIframeRpcChannel(
42
+ useMemo(() => {
43
+ if (typeof window === "undefined") {
44
+ return null;
45
+ }
46
+ const parentOrigin = getParentOrigin();
47
+ if (!parentOrigin) {
48
+ return null;
49
+ }
50
+ const rpcChannel = createIframeRpcChannel({
51
+ subscriberWindow: window,
52
+ channelIdentifier: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,
53
+ version: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,
54
+ getTargetWindow: () => {
55
+ return window.parent === window ? null : window.parent;
56
+ },
57
+ targetOrigin: parentOrigin,
58
+ isExpectedMessage: (event) => {
59
+ return event.source === window.parent && event.origin === parentOrigin;
60
+ },
61
+ outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,
62
+ incomingMethods: websiteBuilderToComponentPreviewMethodConfig,
63
+ handlers: {
64
+ setProps: (input) => onSetPropsRef.current(input)
65
+ }
66
+ });
67
+ return {
68
+ ...rpcChannel,
69
+ subscribe() {
70
+ const unsubscribe = rpcChannel.subscribe();
71
+ createIframeRpcRemote(rpcChannel).iframeReady();
72
+ return unsubscribe;
73
+ }
74
+ };
75
+ }, [])
76
+ );
77
+ }
78
+ function useNavigateAndSettle() {
79
+ const router = useRouter();
80
+ const [isNavigationPending, startNavigation] = useTransition();
81
+ const settleResolvers = useRef([]);
82
+ useEffect(() => {
83
+ if (!isNavigationPending) {
84
+ const resolvers = settleResolvers.current;
85
+ settleResolvers.current = [];
86
+ for (const resolve of resolvers) {
87
+ resolve();
88
+ }
89
+ }
90
+ }, [isNavigationPending]);
91
+ return (href) => {
92
+ return new Promise((resolve) => {
93
+ settleResolvers.current.push(resolve);
94
+ startNavigation(() => {
95
+ router.replace(href, { scroll: false });
96
+ });
97
+ });
98
+ };
99
+ }
100
+ function ComponentPreviewNavigation() {
101
+ const navigateAndSettle = useNavigateAndSettle();
102
+ useComponentPreviewChannel(
103
+ async ({ props, sourceRevision }) => {
104
+ const nextHref = getComponentPreviewHref({
105
+ currentUrl: window.location.href,
106
+ props,
107
+ sourceRevision
108
+ });
109
+ if (nextHref === getCurrentHref()) {
110
+ return { mode: "unchanged" };
111
+ }
112
+ await navigateAndSettle(nextHref);
113
+ return { mode: "navigation" };
114
+ }
115
+ );
116
+ return null;
117
+ }
118
+ function ComponentPreviewStage({
119
+ Component,
120
+ initialProps,
121
+ initialRawProps,
122
+ initialSourceRevision
123
+ }) {
124
+ const [props, setProps] = useState(initialProps);
125
+ const [lastInitialProps, setLastInitialProps] = useState(initialProps);
126
+ if (initialProps !== lastInitialProps) {
127
+ setLastInitialProps(initialProps);
128
+ setProps(initialProps);
129
+ }
130
+ const propsRef = useRef(props);
131
+ propsRef.current = props;
132
+ const lastRawPropsRef = useRef(initialRawProps);
133
+ const lastSourceRevisionRef = useRef(initialSourceRevision);
134
+ const navigateAndSettle = useNavigateAndSettle();
135
+ useComponentPreviewChannel(
136
+ async ({
137
+ props: nextRawProps,
138
+ sourceRevision
139
+ }) => {
140
+ const nextHref = getComponentPreviewHref({
141
+ currentUrl: window.location.href,
142
+ props: nextRawProps,
143
+ sourceRevision
144
+ });
145
+ if (nextHref === getCurrentHref()) {
146
+ return { mode: "unchanged" };
147
+ }
148
+ if (sourceRevision !== lastSourceRevisionRef.current || productReferencesChanged({
149
+ previous: lastRawPropsRef.current,
150
+ next: nextRawProps
151
+ })) {
152
+ await navigateAndSettle(nextHref);
153
+ lastRawPropsRef.current = nextRawProps;
154
+ lastSourceRevisionRef.current = sourceRevision;
155
+ return { mode: "navigation" };
156
+ }
157
+ setProps(
158
+ mergeClientProps({
159
+ currentResolved: propsRef.current,
160
+ nextRaw: nextRawProps
161
+ })
162
+ );
163
+ lastRawPropsRef.current = nextRawProps;
164
+ window.history.replaceState(null, "", nextHref);
165
+ return { mode: "client" };
166
+ }
167
+ );
168
+ return /* @__PURE__ */ jsx(Component, { ...props });
169
+ }
170
+ export {
171
+ ComponentPreviewNavigation,
172
+ ComponentPreviewStage
173
+ };
174
+ //# sourceMappingURL=component-preview-client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../next/component-preview-client.tsx"],
4
+ "sourcesContent": ["\"use client\";\n\nimport type { ComponentType } from \"react\";\nimport type { z } from \"zod\";\n\nimport { useEffect, useMemo, useRef, useState, useTransition } from \"react\";\n\nimport { useRouter } from \"next/navigation\";\n\nimport { useIframeRpcChannel } from \"replo-utils/react/use-iframe-rpc-channel\";\nimport {\n COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,\n COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,\n COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM,\n componentPreviewToWebsiteBuilderMethodConfig,\n websiteBuilderToComponentPreviewMethodConfig,\n} from \"schemas/componentPreview\";\nimport {\n createIframeRpcChannel,\n createIframeRpcRemote,\n} from \"schemas/iframeRpcChannel\";\n\nimport { getComponentPreviewHref } from \"./component-preview-href\";\nimport {\n mergeClientProps,\n productReferencesChanged,\n} from \"./component-preview-props\";\n\ntype SetPropsInput = z.output<\n (typeof websiteBuilderToComponentPreviewMethodConfig)[\"setProps\"][\"inputSchema\"]\n>;\ntype SetPropsResult = z.output<\n (typeof websiteBuilderToComponentPreviewMethodConfig)[\"setProps\"][\"outputSchema\"]\n>;\n\nfunction getParentOrigin(): string | null {\n const fromQuery = new URLSearchParams(window.location.search).get(\n COMPONENT_PREVIEW_PARENT_ORIGIN_PARAM,\n );\n if (fromQuery) {\n try {\n return new URL(fromQuery).origin;\n } catch {\n return null;\n }\n }\n return document.referrer ? new URL(document.referrer).origin : null;\n}\n\nfunction getCurrentHref() {\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n// Child side of the preview channel: announces readiness on subscribe and\n// routes incoming setProps calls to the caller. React Doctor rejects passing a\n// useEffectEvent result into channel handlers, so freshness comes from a plain\n// ref the stable closure reads at call time.\nfunction useComponentPreviewChannel(\n onSetProps: (input: SetPropsInput) => Promise<SetPropsResult>,\n) {\n const onSetPropsRef = useRef(onSetProps);\n onSetPropsRef.current = onSetProps;\n useIframeRpcChannel(\n useMemo(() => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const parentOrigin = getParentOrigin();\n if (!parentOrigin) {\n return null;\n }\n const rpcChannel = createIframeRpcChannel({\n subscriberWindow: window,\n channelIdentifier: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,\n version: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,\n getTargetWindow: () => {\n return window.parent === window ? null : window.parent;\n },\n targetOrigin: parentOrigin,\n isExpectedMessage: (event) => {\n return (\n event.source === window.parent && event.origin === parentOrigin\n );\n },\n outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,\n incomingMethods: websiteBuilderToComponentPreviewMethodConfig,\n handlers: {\n setProps: (input) => onSetPropsRef.current(input),\n },\n });\n return {\n ...rpcChannel,\n subscribe() {\n const unsubscribe = rpcChannel.subscribe();\n createIframeRpcRemote(rpcChannel).iframeReady();\n return unsubscribe;\n },\n };\n }, []),\n );\n}\n\n// Server-render an in-frame navigation and resolve once the transition\n// settles, so the parent's ack tracks the actual repaint.\nfunction useNavigateAndSettle() {\n const router = useRouter();\n const [isNavigationPending, startNavigation] = useTransition();\n const settleResolvers = useRef<(() => void)[]>([]);\n // eslint-disable-next-line replo/no-use-effect -- Resolves promises held by the RPC channel, an external system, once the navigation transition settles.\n useEffect(() => {\n if (!isNavigationPending) {\n const resolvers = settleResolvers.current;\n settleResolvers.current = [];\n for (const resolve of resolvers) {\n resolve();\n }\n }\n }, [isNavigationPending]);\n // A navigation that never settles (a hung RSC fetch) never resolves \u2014 the\n // parent's channel timeout turns that into the disconnected state, and the\n // caller's refs stay uncommitted so the next sync retries the navigation.\n return (href: string) => {\n return new Promise<void>((resolve) => {\n settleResolvers.current.push(resolve);\n startNavigation(() => {\n router.replace(href, { scroll: false });\n });\n });\n };\n}\n\nexport function ComponentPreviewNavigation() {\n const navigateAndSettle = useNavigateAndSettle();\n useComponentPreviewChannel(\n async ({ props, sourceRevision }): Promise<SetPropsResult> => {\n const nextHref = getComponentPreviewHref({\n currentUrl: window.location.href,\n props,\n sourceRevision,\n });\n if (nextHref === getCurrentHref()) {\n return { mode: \"unchanged\" };\n }\n await navigateAndSettle(nextHref);\n return { mode: \"navigation\" };\n },\n );\n return null;\n}\n\nexport function ComponentPreviewStage({\n Component,\n initialProps,\n initialRawProps,\n initialSourceRevision,\n}: {\n Component: ComponentType<Record<string, unknown>>;\n initialProps: Record<string, unknown>;\n initialRawProps: Record<string, unknown>;\n initialSourceRevision: string;\n}) {\n const [props, setProps] = useState(initialProps);\n // A fresh server render (mount or in-frame navigation) is authoritative;\n // its props replace whatever the client store held.\n const [lastInitialProps, setLastInitialProps] = useState(initialProps);\n if (initialProps !== lastInitialProps) {\n setLastInitialProps(initialProps);\n setProps(initialProps);\n }\n const propsRef = useRef(props);\n propsRef.current = props;\n const lastRawPropsRef = useRef(initialRawProps);\n const lastSourceRevisionRef = useRef(initialSourceRevision);\n const navigateAndSettle = useNavigateAndSettle();\n\n useComponentPreviewChannel(\n async ({\n props: nextRawProps,\n sourceRevision,\n }): Promise<SetPropsResult> => {\n const nextHref = getComponentPreviewHref({\n currentUrl: window.location.href,\n props: nextRawProps,\n sourceRevision,\n });\n if (nextHref === getCurrentHref()) {\n return { mode: \"unchanged\" };\n }\n if (\n sourceRevision !== lastSourceRevisionRef.current ||\n productReferencesChanged({\n previous: lastRawPropsRef.current,\n next: nextRawProps,\n })\n ) {\n await navigateAndSettle(nextHref);\n lastRawPropsRef.current = nextRawProps;\n lastSourceRevisionRef.current = sourceRevision;\n return { mode: \"navigation\" };\n }\n setProps(\n mergeClientProps({\n currentResolved: propsRef.current,\n nextRaw: nextRawProps,\n }),\n );\n lastRawPropsRef.current = nextRawProps;\n // Keep the URL canonical without a server round trip, so a manual frame\n // reload restores the current props and the unchanged-check stays exact.\n window.history.replaceState(null, \"\", nextHref);\n return { mode: \"client\" };\n },\n );\n\n return <Component {...props} />;\n}\n"],
5
+ "mappings": ";AAsNS;AAjNT,SAAS,WAAW,SAAS,QAAQ,UAAU,qBAAqB;AAEpE,SAAS,iBAAiB;AAE1B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAiC;AACxC,QAAM,YAAY,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE;AAAA,IAC5D;AAAA,EACF;AACA,MAAI,WAAW;AACb,QAAI;AACF,aAAO,IAAI,IAAI,SAAS,EAAE;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,SAAS,WAAW,IAAI,IAAI,SAAS,QAAQ,EAAE,SAAS;AACjE;AAEA,SAAS,iBAAiB;AACxB,SAAO,GAAG,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,MAAM,GAAG,OAAO,SAAS,IAAI;AACpF;AAMA,SAAS,2BACP,YACA;AACA,QAAM,gBAAgB,OAAO,UAAU;AACvC,gBAAc,UAAU;AACxB;AAAA,IACE,QAAQ,MAAM;AACZ,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,MACT;AACA,YAAM,eAAe,gBAAgB;AACrC,UAAI,CAAC,cAAc;AACjB,eAAO;AAAA,MACT;AACA,YAAM,aAAa,uBAAuB;AAAA,QACxC,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,SAAS;AAAA,QACT,iBAAiB,MAAM;AACrB,iBAAO,OAAO,WAAW,SAAS,OAAO,OAAO;AAAA,QAClD;AAAA,QACA,cAAc;AAAA,QACd,mBAAmB,CAAC,UAAU;AAC5B,iBACE,MAAM,WAAW,OAAO,UAAU,MAAM,WAAW;AAAA,QAEvD;AAAA,QACA,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,QACjB,UAAU;AAAA,UACR,UAAU,CAAC,UAAU,cAAc,QAAQ,KAAK;AAAA,QAClD;AAAA,MACF,CAAC;AACD,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AACV,gBAAM,cAAc,WAAW,UAAU;AACzC,gCAAsB,UAAU,EAAE,YAAY;AAC9C,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF,GAAG,CAAC,CAAC;AAAA,EACP;AACF;AAIA,SAAS,uBAAuB;AAC9B,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,qBAAqB,eAAe,IAAI,cAAc;AAC7D,QAAM,kBAAkB,OAAuB,CAAC,CAAC;AAEjD,YAAU,MAAM;AACd,QAAI,CAAC,qBAAqB;AACxB,YAAM,YAAY,gBAAgB;AAClC,sBAAgB,UAAU,CAAC;AAC3B,iBAAW,WAAW,WAAW;AAC/B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF,GAAG,CAAC,mBAAmB,CAAC;AAIxB,SAAO,CAAC,SAAiB;AACvB,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,sBAAgB,QAAQ,KAAK,OAAO;AACpC,sBAAgB,MAAM;AACpB,eAAO,QAAQ,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,MACxC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAEO,SAAS,6BAA6B;AAC3C,QAAM,oBAAoB,qBAAqB;AAC/C;AAAA,IACE,OAAO,EAAE,OAAO,eAAe,MAA+B;AAC5D,YAAM,WAAW,wBAAwB;AAAA,QACvC,YAAY,OAAO,SAAS;AAAA,QAC5B;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,aAAa,eAAe,GAAG;AACjC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AACA,YAAM,kBAAkB,QAAQ;AAChC,aAAO,EAAE,MAAM,aAAa;AAAA,IAC9B;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,YAAY;AAG/C,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,SAAS,YAAY;AACrE,MAAI,iBAAiB,kBAAkB;AACrC,wBAAoB,YAAY;AAChC,aAAS,YAAY;AAAA,EACvB;AACA,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AACnB,QAAM,kBAAkB,OAAO,eAAe;AAC9C,QAAM,wBAAwB,OAAO,qBAAqB;AAC1D,QAAM,oBAAoB,qBAAqB;AAE/C;AAAA,IACE,OAAO;AAAA,MACL,OAAO;AAAA,MACP;AAAA,IACF,MAA+B;AAC7B,YAAM,WAAW,wBAAwB;AAAA,QACvC,YAAY,OAAO,SAAS;AAAA,QAC5B,OAAO;AAAA,QACP;AAAA,MACF,CAAC;AACD,UAAI,aAAa,eAAe,GAAG;AACjC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AACA,UACE,mBAAmB,sBAAsB,WACzC,yBAAyB;AAAA,QACvB,UAAU,gBAAgB;AAAA,QAC1B,MAAM;AAAA,MACR,CAAC,GACD;AACA,cAAM,kBAAkB,QAAQ;AAChC,wBAAgB,UAAU;AAC1B,8BAAsB,UAAU;AAChC,eAAO,EAAE,MAAM,aAAa;AAAA,MAC9B;AACA;AAAA,QACE,iBAAiB;AAAA,UACf,iBAAiB,SAAS;AAAA,UAC1B,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AACA,sBAAgB,UAAU;AAG1B,aAAO,QAAQ,aAAa,MAAM,IAAI,QAAQ;AAC9C,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO,oBAAC,aAAW,GAAG,OAAO;AAC/B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,8 @@
1
+ export declare function productReferencesChanged({ previous, next, }: {
2
+ previous: Record<string, unknown>;
3
+ next: Record<string, unknown>;
4
+ }): boolean;
5
+ export declare function mergeClientProps({ currentResolved, nextRaw, }: {
6
+ currentResolved: Record<string, unknown>;
7
+ nextRaw: Record<string, unknown>;
8
+ }): Record<string, unknown>;
@@ -0,0 +1,36 @@
1
+ import { componentPreviewProductReferenceSchema } from "../_vendor/schemas/componentPreview.mjs";
2
+ function isProductReference(value) {
3
+ return componentPreviewProductReferenceSchema.safeParse(value).success;
4
+ }
5
+ function productReferencesChanged({
6
+ previous,
7
+ next
8
+ }) {
9
+ const names = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)]);
10
+ for (const name of names) {
11
+ const previousIsReference = isProductReference(previous[name]);
12
+ const nextIsReference = isProductReference(next[name]);
13
+ if (!previousIsReference && !nextIsReference) {
14
+ continue;
15
+ }
16
+ if (previousIsReference !== nextIsReference || JSON.stringify(previous[name]) !== JSON.stringify(next[name])) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+ function mergeClientProps({
23
+ currentResolved,
24
+ nextRaw
25
+ }) {
26
+ return Object.fromEntries(
27
+ Object.entries(nextRaw).map(([name, value]) => {
28
+ return isProductReference(value) ? [name, currentResolved[name]] : [name, value];
29
+ })
30
+ );
31
+ }
32
+ export {
33
+ mergeClientProps,
34
+ productReferencesChanged
35
+ };
36
+ //# sourceMappingURL=component-preview-props.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../next/component-preview-props.ts"],
4
+ "sourcesContent": ["import { componentPreviewProductReferenceSchema } from \"schemas/componentPreview\";\n\nfunction isProductReference(value: unknown) {\n return componentPreviewProductReferenceSchema.safeParse(value).success;\n}\n\n// Product references need the server loader, so any change to one forces the\n// navigation path; everything else can apply in place.\nexport function productReferencesChanged({\n previous,\n next,\n}: {\n previous: Record<string, unknown>;\n next: Record<string, unknown>;\n}): boolean {\n const names = new Set([...Object.keys(previous), ...Object.keys(next)]);\n for (const name of names) {\n const previousIsReference = isProductReference(previous[name]);\n const nextIsReference = isProductReference(next[name]);\n if (!previousIsReference && !nextIsReference) {\n continue;\n }\n if (\n previousIsReference !== nextIsReference ||\n JSON.stringify(previous[name]) !== JSON.stringify(next[name])\n ) {\n return true;\n }\n }\n return false;\n}\n\n// Builds the next client-store props from an incoming raw payload: raw product\n// references are swapped for the already-resolved values in the current store\n// (only unchanged references reach this path \u2014 changed ones forced navigation),\n// and every other prop takes the incoming value. Keys absent from the payload\n// are dropped so the component's own defaults return.\nexport function mergeClientProps({\n currentResolved,\n nextRaw,\n}: {\n currentResolved: Record<string, unknown>;\n nextRaw: Record<string, unknown>;\n}): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(nextRaw).map(([name, value]) => {\n return isProductReference(value)\n ? [name, currentResolved[name]]\n : [name, value];\n }),\n );\n}\n"],
5
+ "mappings": "AAAA,SAAS,8CAA8C;AAEvD,SAAS,mBAAmB,OAAgB;AAC1C,SAAO,uCAAuC,UAAU,KAAK,EAAE;AACjE;AAIO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGY;AACV,QAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,GAAG,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC;AACtE,aAAW,QAAQ,OAAO;AACxB,UAAM,sBAAsB,mBAAmB,SAAS,IAAI,CAAC;AAC7D,UAAM,kBAAkB,mBAAmB,KAAK,IAAI,CAAC;AACrD,QAAI,CAAC,uBAAuB,CAAC,iBAAiB;AAC5C;AAAA,IACF;AACA,QACE,wBAAwB,mBACxB,KAAK,UAAU,SAAS,IAAI,CAAC,MAAM,KAAK,UAAU,KAAK,IAAI,CAAC,GAC5D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAG4B;AAC1B,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;AAC7C,aAAO,mBAAmB,KAAK,IAC3B,CAAC,MAAM,gBAAgB,IAAI,CAAC,IAC5B,CAAC,MAAM,KAAK;AAAA,IAClB,CAAC;AAAA,EACH;AACF;",
6
+ "names": []
7
+ }
@@ -1,9 +1,11 @@
1
1
  import type { ComponentType } from "react";
2
2
  import type { ComponentPreviewProductReference } from "../_vendor/schemas/generated/componentPreview";
3
3
  export declare function createComponentPreviewPage(Component: ComponentType<Record<string, unknown>>, options?: {
4
- getProduct: (reference: ComponentPreviewProductReference) => Promise<unknown>;
4
+ getProduct?: (reference: ComponentPreviewProductReference) => Promise<unknown>;
5
+ interactive?: boolean;
5
6
  }): ({ searchParams, }: {
6
7
  searchParams: Promise<{
7
8
  props?: string | string[];
9
+ sourceRevision?: string | string[];
8
10
  }>;
9
11
  }) => Promise<import("react/jsx-runtime").JSX.Element>;
@@ -6,48 +6,43 @@ import {
6
6
  } from "../_vendor/schemas/componentPreview.mjs";
7
7
  import { DocumentReadyBeacon } from "../DocumentReadyBeacon";
8
8
  import { NextErrorReporter } from "../NextErrorReporter";
9
- import { ComponentPreviewNavigation } from "./component-preview-navigation";
10
- const BLOCKED_PROP_NAMES = /* @__PURE__ */ new Set([
11
- "__proto__",
12
- "constructor",
13
- "dangerouslySetInnerHTML"
14
- ]);
9
+ import {
10
+ ComponentPreviewNavigation,
11
+ ComponentPreviewStage
12
+ } from "./component-preview-client";
15
13
  async function getPreviewProps({
16
14
  value,
17
15
  getProduct
18
16
  }) {
19
17
  const previewPropsResult = componentPreviewPropsSchema.safeParse(value);
20
18
  if (!previewPropsResult.success) {
21
- return {};
19
+ return { rawProps: {}, resolvedProps: {} };
22
20
  }
23
- const previewProps = {};
21
+ const rawProps = {};
22
+ const resolvedProps = {};
24
23
  for (const [name, propValue] of Object.entries(previewPropsResult.data)) {
25
- if (BLOCKED_PROP_NAMES.has(name)) {
26
- continue;
27
- }
24
+ rawProps[name] = propValue;
28
25
  const productReferenceResult = componentPreviewProductReferenceSchema.safeParse(propValue);
29
26
  if (!productReferenceResult.success) {
30
- previewProps[name] = propValue;
27
+ resolvedProps[name] = propValue;
31
28
  continue;
32
29
  }
33
- if (getProduct) {
34
- previewProps[name] = await getProduct(productReferenceResult.data);
35
- }
30
+ resolvedProps[name] = getProduct ? await getProduct(productReferenceResult.data) : propValue;
36
31
  }
37
- return previewProps;
32
+ return { rawProps, resolvedProps };
38
33
  }
39
34
  async function parseProps({
40
35
  value,
41
36
  getProduct
42
37
  }) {
43
38
  if (typeof value !== "string" || new TextEncoder().encode(value).byteLength > MAX_COMPONENT_PREVIEW_PROPS_BYTES) {
44
- return {};
39
+ return { rawProps: {}, resolvedProps: {} };
45
40
  }
46
41
  let parsed;
47
42
  try {
48
43
  parsed = JSON.parse(value);
49
44
  } catch {
50
- return {};
45
+ return { rawProps: {}, resolvedProps: {} };
51
46
  }
52
47
  return await getPreviewProps({ value: parsed, getProduct });
53
48
  }
@@ -55,15 +50,31 @@ function createComponentPreviewPage(Component, options) {
55
50
  return async function ComponentPreviewPage({
56
51
  searchParams
57
52
  }) {
58
- const props = await parseProps({
59
- value: (await searchParams).props,
53
+ const resolvedSearchParams = await searchParams;
54
+ const { rawProps, resolvedProps } = await parseProps({
55
+ value: resolvedSearchParams.props,
60
56
  getProduct: options?.getProduct
61
57
  });
58
+ if (options?.interactive) {
59
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
60
+ /* @__PURE__ */ jsx(DocumentReadyBeacon, {}),
61
+ /* @__PURE__ */ jsx(NextErrorReporter, {}),
62
+ /* @__PURE__ */ jsx(
63
+ ComponentPreviewStage,
64
+ {
65
+ Component,
66
+ initialProps: resolvedProps,
67
+ initialRawProps: rawProps,
68
+ initialSourceRevision: typeof resolvedSearchParams.sourceRevision === "string" ? resolvedSearchParams.sourceRevision : ""
69
+ }
70
+ )
71
+ ] });
72
+ }
62
73
  return /* @__PURE__ */ jsxs(Fragment, { children: [
63
74
  /* @__PURE__ */ jsx(ComponentPreviewNavigation, {}),
64
75
  /* @__PURE__ */ jsx(DocumentReadyBeacon, {}),
65
76
  /* @__PURE__ */ jsx(NextErrorReporter, {}),
66
- /* @__PURE__ */ jsx(Component, { ...props })
77
+ /* @__PURE__ */ jsx(Component, { ...resolvedProps })
67
78
  ] });
68
79
  };
69
80
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../next/component-preview.tsx"],
4
- "sourcesContent": ["import type { ComponentType } from \"react\";\nimport type { ComponentPreviewProductReference } from \"schemas/generated/componentPreview\";\n\nimport {\n componentPreviewProductReferenceSchema,\n componentPreviewPropsSchema,\n MAX_COMPONENT_PREVIEW_PROPS_BYTES,\n} from \"schemas/componentPreview\";\n\nimport { DocumentReadyBeacon } from \"../DocumentReadyBeacon\";\nimport { NextErrorReporter } from \"../NextErrorReporter\";\nimport { ComponentPreviewNavigation } from \"./component-preview-navigation\";\n\nconst BLOCKED_PROP_NAMES = new Set([\n \"__proto__\",\n \"constructor\",\n \"dangerouslySetInnerHTML\",\n]);\n\nasync function getPreviewProps({\n value,\n getProduct,\n}: {\n value: unknown;\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n}): Promise<Record<string, unknown>> {\n const previewPropsResult = componentPreviewPropsSchema.safeParse(value);\n if (!previewPropsResult.success) {\n return {};\n }\n\n const previewProps: Record<string, unknown> = {};\n for (const [name, propValue] of Object.entries(previewPropsResult.data)) {\n if (BLOCKED_PROP_NAMES.has(name)) {\n continue;\n }\n const productReferenceResult =\n componentPreviewProductReferenceSchema.safeParse(propValue);\n if (!productReferenceResult.success) {\n previewProps[name] = propValue;\n continue;\n }\n if (getProduct) {\n previewProps[name] = await getProduct(productReferenceResult.data);\n }\n }\n return previewProps;\n}\n\nasync function parseProps({\n value,\n getProduct,\n}: {\n value: string | string[] | undefined;\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n}) {\n if (\n typeof value !== \"string\" ||\n new TextEncoder().encode(value).byteLength >\n MAX_COMPONENT_PREVIEW_PROPS_BYTES\n ) {\n return {};\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return {};\n }\n return await getPreviewProps({ value: parsed, getProduct });\n}\n\nexport function createComponentPreviewPage(\n Component: ComponentType<Record<string, unknown>>,\n options?: {\n getProduct: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n },\n) {\n return async function ComponentPreviewPage({\n searchParams,\n }: {\n searchParams: Promise<{ props?: string | string[] }>;\n }) {\n const props = await parseProps({\n value: (await searchParams).props,\n getProduct: options?.getProduct,\n });\n return (\n <>\n <ComponentPreviewNavigation />\n <DocumentReadyBeacon />\n <NextErrorReporter />\n <Component {...props} />\n </>\n );\n };\n}\n"],
5
- "mappings": "AA8FM,mBACE,KADF;AA3FN;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAE3C,MAAM,qBAAqB,oBAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,eAAe,gBAAgB;AAAA,EAC7B;AAAA,EACA;AACF,GAKqC;AACnC,QAAM,qBAAqB,4BAA4B,UAAU,KAAK;AACtE,MAAI,CAAC,mBAAmB,SAAS;AAC/B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,eAAwC,CAAC;AAC/C,aAAW,CAAC,MAAM,SAAS,KAAK,OAAO,QAAQ,mBAAmB,IAAI,GAAG;AACvE,QAAI,mBAAmB,IAAI,IAAI,GAAG;AAChC;AAAA,IACF;AACA,UAAM,yBACJ,uCAAuC,UAAU,SAAS;AAC5D,QAAI,CAAC,uBAAuB,SAAS;AACnC,mBAAa,IAAI,IAAI;AACrB;AAAA,IACF;AACA,QAAI,YAAY;AACd,mBAAa,IAAI,IAAI,MAAM,WAAW,uBAAuB,IAAI;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,WAAW;AAAA,EACxB;AAAA,EACA;AACF,GAKG;AACD,MACE,OAAO,UAAU,YACjB,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE,aAC9B,mCACF;AACA,WAAO,CAAC;AAAA,EACV;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK;AAAA,EAC3B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,SAAO,MAAM,gBAAgB,EAAE,OAAO,QAAQ,WAAW,CAAC;AAC5D;AAEO,SAAS,2BACd,WACA,SAKA;AACA,SAAO,eAAe,qBAAqB;AAAA,IACzC;AAAA,EACF,GAEG;AACD,UAAM,QAAQ,MAAM,WAAW;AAAA,MAC7B,QAAQ,MAAM,cAAc;AAAA,MAC5B,YAAY,SAAS;AAAA,IACvB,CAAC;AACD,WACE,iCACE;AAAA,0BAAC,8BAA2B;AAAA,MAC5B,oBAAC,uBAAoB;AAAA,MACrB,oBAAC,qBAAkB;AAAA,MACnB,oBAAC,aAAW,GAAG,OAAO;AAAA,OACxB;AAAA,EAEJ;AACF;",
4
+ "sourcesContent": ["import type { ComponentType } from \"react\";\nimport type { ComponentPreviewProductReference } from \"schemas/generated/componentPreview\";\n\nimport {\n componentPreviewProductReferenceSchema,\n componentPreviewPropsSchema,\n MAX_COMPONENT_PREVIEW_PROPS_BYTES,\n} from \"schemas/componentPreview\";\n\nimport { DocumentReadyBeacon } from \"../DocumentReadyBeacon\";\nimport { NextErrorReporter } from \"../NextErrorReporter\";\nimport {\n ComponentPreviewNavigation,\n ComponentPreviewStage,\n} from \"./component-preview-client\";\n\nasync function getPreviewProps({\n value,\n getProduct,\n}: {\n value: unknown;\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n}): Promise<{\n rawProps: Record<string, unknown>;\n resolvedProps: Record<string, unknown>;\n}> {\n const previewPropsResult = componentPreviewPropsSchema.safeParse(value);\n if (!previewPropsResult.success) {\n return { rawProps: {}, resolvedProps: {} };\n }\n\n const rawProps: Record<string, unknown> = {};\n const resolvedProps: Record<string, unknown> = {};\n for (const [name, propValue] of Object.entries(previewPropsResult.data)) {\n rawProps[name] = propValue;\n const productReferenceResult =\n componentPreviewProductReferenceSchema.safeParse(propValue);\n if (!productReferenceResult.success) {\n resolvedProps[name] = propValue;\n continue;\n }\n // Without a resolver the raw reference passes through rather than the prop\n // silently vanishing; the component sees exactly what the URL carried.\n resolvedProps[name] = getProduct\n ? await getProduct(productReferenceResult.data)\n : propValue;\n }\n return { rawProps, resolvedProps };\n}\n\nasync function parseProps({\n value,\n getProduct,\n}: {\n value: string | string[] | undefined;\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n}) {\n if (\n typeof value !== \"string\" ||\n new TextEncoder().encode(value).byteLength >\n MAX_COMPONENT_PREVIEW_PROPS_BYTES\n ) {\n return { rawProps: {}, resolvedProps: {} };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return { rawProps: {}, resolvedProps: {} };\n }\n return await getPreviewProps({ value: parsed, getProduct });\n}\n\nexport function createComponentPreviewPage(\n Component: ComponentType<Record<string, unknown>>,\n options?: {\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n // Interactive components (\"use client\" in the component file) render\n // inside the client prop stage so plain prop edits apply without a server\n // round trip. Server components can only re-render via navigation.\n interactive?: boolean;\n },\n) {\n return async function ComponentPreviewPage({\n searchParams,\n }: {\n searchParams: Promise<{\n props?: string | string[];\n sourceRevision?: string | string[];\n }>;\n }) {\n const resolvedSearchParams = await searchParams;\n const { rawProps, resolvedProps } = await parseProps({\n value: resolvedSearchParams.props,\n getProduct: options?.getProduct,\n });\n if (options?.interactive) {\n return (\n <>\n <DocumentReadyBeacon />\n <NextErrorReporter />\n <ComponentPreviewStage\n Component={Component}\n initialProps={resolvedProps}\n initialRawProps={rawProps}\n initialSourceRevision={\n typeof resolvedSearchParams.sourceRevision === \"string\"\n ? resolvedSearchParams.sourceRevision\n : \"\"\n }\n />\n </>\n );\n }\n return (\n <>\n <ComponentPreviewNavigation />\n <DocumentReadyBeacon />\n <NextErrorReporter />\n <Component {...resolvedProps} />\n </>\n );\n };\n}\n"],
5
+ "mappings": "AAwGQ,mBACE,KADF;AArGR;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,eAAe,gBAAgB;AAAA,EAC7B;AAAA,EACA;AACF,GAQG;AACD,QAAM,qBAAqB,4BAA4B,UAAU,KAAK;AACtE,MAAI,CAAC,mBAAmB,SAAS;AAC/B,WAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAAA,EAC3C;AAEA,QAAM,WAAoC,CAAC;AAC3C,QAAM,gBAAyC,CAAC;AAChD,aAAW,CAAC,MAAM,SAAS,KAAK,OAAO,QAAQ,mBAAmB,IAAI,GAAG;AACvE,aAAS,IAAI,IAAI;AACjB,UAAM,yBACJ,uCAAuC,UAAU,SAAS;AAC5D,QAAI,CAAC,uBAAuB,SAAS;AACnC,oBAAc,IAAI,IAAI;AACtB;AAAA,IACF;AAGA,kBAAc,IAAI,IAAI,aAClB,MAAM,WAAW,uBAAuB,IAAI,IAC5C;AAAA,EACN;AACA,SAAO,EAAE,UAAU,cAAc;AACnC;AAEA,eAAe,WAAW;AAAA,EACxB;AAAA,EACA;AACF,GAKG;AACD,MACE,OAAO,UAAU,YACjB,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE,aAC9B,mCACF;AACA,WAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAAA,EAC3C;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK;AAAA,EAC3B,QAAQ;AACN,WAAO,EAAE,UAAU,CAAC,GAAG,eAAe,CAAC,EAAE;AAAA,EAC3C;AACA,SAAO,MAAM,gBAAgB,EAAE,OAAO,QAAQ,WAAW,CAAC;AAC5D;AAEO,SAAS,2BACd,WACA,SASA;AACA,SAAO,eAAe,qBAAqB;AAAA,IACzC;AAAA,EACF,GAKG;AACD,UAAM,uBAAuB,MAAM;AACnC,UAAM,EAAE,UAAU,cAAc,IAAI,MAAM,WAAW;AAAA,MACnD,OAAO,qBAAqB;AAAA,MAC5B,YAAY,SAAS;AAAA,IACvB,CAAC;AACD,QAAI,SAAS,aAAa;AACxB,aACE,iCACE;AAAA,4BAAC,uBAAoB;AAAA,QACrB,oBAAC,qBAAkB;AAAA,QACnB;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,cAAc;AAAA,YACd,iBAAiB;AAAA,YACjB,uBACE,OAAO,qBAAqB,mBAAmB,WAC3C,qBAAqB,iBACrB;AAAA;AAAA,QAER;AAAA,SACF;AAAA,IAEJ;AACA,WACE,iCACE;AAAA,0BAAC,8BAA2B;AAAA,MAC5B,oBAAC,uBAAoB;AAAA,MACrB,oBAAC,qBAAkB;AAAA,MACnB,oBAAC,aAAW,GAAG,eAAe;AAAA,OAChC;AAAA,EAEJ;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sandbox-runtime",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sandbox-runtime",
6
- "version": "1.0.1",
6
+ "version": "1.2.0",
7
7
  "branch": "HEAD",
8
- "commit": "9fc57a3ef1d06689e70fe0d90c25c03717e37d03",
9
- "builtAt": "2026-08-19T22:39:33.469Z"
8
+ "commit": "0460cd76d21b76aeefd6d972a25dab1b917d34d0",
9
+ "builtAt": "2026-08-25T19:38:53.050Z"
10
10
  },
11
11
  "description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",
@@ -21,6 +21,10 @@
21
21
  "types": "./next/component-preview.d.ts",
22
22
  "default": "./next/component-preview.js"
23
23
  },
24
+ "./next/component-preview-client": {
25
+ "types": "./next/component-preview-client.d.ts",
26
+ "default": "./next/component-preview-client.js"
27
+ },
24
28
  "./next/config": {
25
29
  "types": "./next/config.d.ts",
26
30
  "default": "./next/config.js"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../select-mode/displayModeStore.ts"],
4
- "sourcesContent": ["\"use client\";\n\nimport type { DisplayMode } from \"schemas/generated/selectMode\";\n\nimport { useSyncExternalStore } from \"react\";\n\n/**\n * Framework-agnostic singleton for the preview iframe's select-mode display\n * mode (\"browse\" vs \"selection\"). It lives as a module singleton so that\n * components that don't share a provider ancestor \u2014 `EditorSelection` (which\n * owns the pointer/selection machinery) and `LinkInterceptor` (a sibling under\n * `ReploProvider`) \u2014 can agree on whether the user is currently selecting.\n *\n * `LinkInterceptor` needs this because in agent-preview mode it turns\n * same-origin link clicks into a `replo:navigate` postMessage (SPA-style\n * navigation of the preview). While select mode is active that navigation must\n * stand down so a link click selects the element instead of leaving the page \u2014\n * the two capture-phase click listeners otherwise race, and `stopPropagation`\n * can't suppress a sibling listener on the same node.\n *\n * @author Noah Gilmore 2026-07-23\n */\n\nconst listeners = new Set<() => void>();\nlet displayMode: DisplayMode = \"browse\";\n\n// select-mode DOM behavior is client-only, so the server snapshot is a stable\n// constant and never causes a hydration mismatch.\nconst SERVER_SNAPSHOT: DisplayMode = \"browse\";\n\nfunction notify() {\n for (const listener of listeners) {\n listener();\n }\n}\n\nexport const displayModeStore = {\n getSnapshot(): DisplayMode {\n return displayMode;\n },\n getServerSnapshot(): DisplayMode {\n return SERVER_SNAPSHOT;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n setDisplayMode(nextDisplayMode: DisplayMode) {\n if (nextDisplayMode === displayMode) {\n return;\n }\n displayMode = nextDisplayMode;\n notify();\n },\n /** Test-only: reset back to the default so specs don't leak state. */\n reset() {\n displayMode = \"browse\";\n },\n};\n\nexport function useDisplayMode(): DisplayMode {\n return useSyncExternalStore(\n displayModeStore.subscribe,\n displayModeStore.getSnapshot,\n displayModeStore.getServerSnapshot,\n );\n}\n"],
4
+ "sourcesContent": ["\"use client\";\n\nimport type { DisplayMode } from \"schemas/generated/selectMode\";\n\nimport { useSyncExternalStore } from \"react\";\n\n/**\n * Framework-agnostic singleton for the preview iframe's select-mode display\n * mode (\"browse\" vs \"selection\"). It lives as a module singleton so that\n * components that don't share a provider ancestor \u2014 `EditorSelection` (which\n * owns the pointer/selection machinery) and `LinkInterceptor` (a sibling under\n * `ReploProvider`) \u2014 can agree on whether the user is currently selecting.\n *\n * `LinkInterceptor` needs this because in agent-preview mode it observes\n * same-origin link clicks and reports the resulting navigation to the parent\n * editor (`replo:navigated`). While select mode is active that must stand\n * down so a link click selects the element instead of leaving the page \u2014\n * the two capture-phase click listeners otherwise race, and `stopPropagation`\n * can't suppress a sibling listener on the same node.\n *\n * @author Noah Gilmore 2026-07-23\n */\n\nconst listeners = new Set<() => void>();\nlet displayMode: DisplayMode = \"browse\";\n\n// select-mode DOM behavior is client-only, so the server snapshot is a stable\n// constant and never causes a hydration mismatch.\nconst SERVER_SNAPSHOT: DisplayMode = \"browse\";\n\nfunction notify() {\n for (const listener of listeners) {\n listener();\n }\n}\n\nexport const displayModeStore = {\n getSnapshot(): DisplayMode {\n return displayMode;\n },\n getServerSnapshot(): DisplayMode {\n return SERVER_SNAPSHOT;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n setDisplayMode(nextDisplayMode: DisplayMode) {\n if (nextDisplayMode === displayMode) {\n return;\n }\n displayMode = nextDisplayMode;\n notify();\n },\n /** Test-only: reset back to the default so specs don't leak state. */\n reset() {\n displayMode = \"browse\";\n },\n};\n\nexport function useDisplayMode(): DisplayMode {\n return useSyncExternalStore(\n displayModeStore.subscribe,\n displayModeStore.getSnapshot,\n displayModeStore.getServerSnapshot,\n );\n}\n"],
5
5
  "mappings": ";AAIA,SAAS,4BAA4B;AAmBrC,MAAM,YAAY,oBAAI,IAAgB;AACtC,IAAI,cAA2B;AAI/B,MAAM,kBAA+B;AAErC,SAAS,SAAS;AAChB,aAAW,YAAY,WAAW;AAChC,aAAS;AAAA,EACX;AACF;AAEO,MAAM,mBAAmB;AAAA,EAC9B,cAA2B;AACzB,WAAO;AAAA,EACT;AAAA,EACA,oBAAiC;AAC/B,WAAO;AAAA,EACT;AAAA,EACA,UAAU,UAAkC;AAC1C,cAAU,IAAI,QAAQ;AACtB,WAAO,MAAM;AACX,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAAA,EACA,eAAe,iBAA8B;AAC3C,QAAI,oBAAoB,aAAa;AACnC;AAAA,IACF;AACA,kBAAc;AACd,WAAO;AAAA,EACT;AAAA;AAAA,EAEA,QAAQ;AACN,kBAAc;AAAA,EAChB;AACF;AAEO,SAAS,iBAA8B;AAC5C,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB;AACF;",
6
6
  "names": []
7
7
  }
@@ -1 +0,0 @@
1
- export declare function ComponentPreviewNavigation(): null;
@@ -1,66 +0,0 @@
1
- "use client";
2
- import { useMemo } from "react";
3
- import { useRouter } from "next/navigation";
4
- import { useIframeRpcChannel } from "../_vendor/replo-utils/react/use-iframe-rpc-channel.mjs";
5
- import {
6
- COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,
7
- COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,
8
- componentPreviewToWebsiteBuilderMethodConfig,
9
- websiteBuilderToComponentPreviewMethodConfig
10
- } from "../_vendor/schemas/componentPreview.mjs";
11
- import {
12
- createIframeRpcChannel,
13
- createIframeRpcRemote
14
- } from "../_vendor/schemas/iframeRpcChannel.mjs";
15
- import { getComponentPreviewHref } from "./component-preview-href";
16
- function ComponentPreviewNavigation() {
17
- const router = useRouter();
18
- useIframeRpcChannel(
19
- useMemo(() => {
20
- if (typeof window === "undefined" || !document.referrer) {
21
- return null;
22
- }
23
- const parentOrigin = new URL(document.referrer).origin;
24
- const rpcChannel = createIframeRpcChannel({
25
- subscriberWindow: window,
26
- channelIdentifier: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,
27
- version: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,
28
- getTargetWindow: () => {
29
- return window.parent === window ? null : window.parent;
30
- },
31
- targetOrigin: parentOrigin,
32
- isExpectedMessage: (event) => {
33
- return event.source === window.parent && event.origin === parentOrigin;
34
- },
35
- outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,
36
- incomingMethods: websiteBuilderToComponentPreviewMethodConfig,
37
- handlers: {
38
- setProps: ({ props, sourceRevision }) => {
39
- const nextHref = getComponentPreviewHref({
40
- currentUrl: window.location.href,
41
- props,
42
- sourceRevision
43
- });
44
- const currentHref = `${window.location.pathname}${window.location.search}${window.location.hash}`;
45
- if (nextHref !== currentHref) {
46
- router.replace(nextHref, { scroll: false });
47
- }
48
- }
49
- }
50
- });
51
- return {
52
- ...rpcChannel,
53
- subscribe() {
54
- const unsubscribe = rpcChannel.subscribe();
55
- createIframeRpcRemote(rpcChannel).iframeReady();
56
- return unsubscribe;
57
- }
58
- };
59
- }, [router])
60
- );
61
- return null;
62
- }
63
- export {
64
- ComponentPreviewNavigation
65
- };
66
- //# sourceMappingURL=component-preview-navigation.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../next/component-preview-navigation.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport { useMemo } from \"react\";\n\nimport { useRouter } from \"next/navigation\";\n\nimport { useIframeRpcChannel } from \"replo-utils/react/use-iframe-rpc-channel\";\nimport {\n COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,\n COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,\n componentPreviewToWebsiteBuilderMethodConfig,\n websiteBuilderToComponentPreviewMethodConfig,\n} from \"schemas/componentPreview\";\nimport {\n createIframeRpcChannel,\n createIframeRpcRemote,\n} from \"schemas/iframeRpcChannel\";\n\nimport { getComponentPreviewHref } from \"./component-preview-href\";\n\nexport function ComponentPreviewNavigation() {\n const router = useRouter();\n useIframeRpcChannel(\n useMemo(() => {\n if (typeof window === \"undefined\" || !document.referrer) {\n return null;\n }\n const parentOrigin = new URL(document.referrer).origin;\n const rpcChannel = createIframeRpcChannel({\n subscriberWindow: window,\n channelIdentifier: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,\n version: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,\n getTargetWindow: () => {\n return window.parent === window ? null : window.parent;\n },\n targetOrigin: parentOrigin,\n isExpectedMessage: (event) => {\n return (\n event.source === window.parent && event.origin === parentOrigin\n );\n },\n outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,\n incomingMethods: websiteBuilderToComponentPreviewMethodConfig,\n handlers: {\n setProps: ({ props, sourceRevision }) => {\n const nextHref = getComponentPreviewHref({\n currentUrl: window.location.href,\n props,\n sourceRevision,\n });\n const currentHref = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n if (nextHref !== currentHref) {\n router.replace(nextHref, { scroll: false });\n }\n },\n },\n });\n return {\n ...rpcChannel,\n subscribe() {\n const unsubscribe = rpcChannel.subscribe();\n createIframeRpcRemote(rpcChannel).iframeReady();\n return unsubscribe;\n },\n };\n }, [router]),\n );\n\n return null;\n}\n"],
5
- "mappings": ";AAEA,SAAS,eAAe;AAExB,SAAS,iBAAiB;AAE1B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,+BAA+B;AAEjC,SAAS,6BAA6B;AAC3C,QAAM,SAAS,UAAU;AACzB;AAAA,IACE,QAAQ,MAAM;AACZ,UAAI,OAAO,WAAW,eAAe,CAAC,SAAS,UAAU;AACvD,eAAO;AAAA,MACT;AACA,YAAM,eAAe,IAAI,IAAI,SAAS,QAAQ,EAAE;AAChD,YAAM,aAAa,uBAAuB;AAAA,QACxC,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,SAAS;AAAA,QACT,iBAAiB,MAAM;AACrB,iBAAO,OAAO,WAAW,SAAS,OAAO,OAAO;AAAA,QAClD;AAAA,QACA,cAAc;AAAA,QACd,mBAAmB,CAAC,UAAU;AAC5B,iBACE,MAAM,WAAW,OAAO,UAAU,MAAM,WAAW;AAAA,QAEvD;AAAA,QACA,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,QACjB,UAAU;AAAA,UACR,UAAU,CAAC,EAAE,OAAO,eAAe,MAAM;AACvC,kBAAM,WAAW,wBAAwB;AAAA,cACvC,YAAY,OAAO,SAAS;AAAA,cAC5B;AAAA,cACA;AAAA,YACF,CAAC;AACD,kBAAM,cAAc,GAAG,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,MAAM,GAAG,OAAO,SAAS,IAAI;AAC/F,gBAAI,aAAa,aAAa;AAC5B,qBAAO,QAAQ,UAAU,EAAE,QAAQ,MAAM,CAAC;AAAA,YAC5C;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AACD,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AACV,gBAAM,cAAc,WAAW,UAAU;AACzC,gCAAsB,UAAU,EAAE,YAAY;AAC9C,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF,GAAG,CAAC,MAAM,CAAC;AAAA,EACb;AAEA,SAAO;AACT;",
6
- "names": []
7
- }