@replohq/sandbox-runtime 0.1.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.
- package/ClientErrorBoundary.d.ts +10 -0
- package/ClientErrorBoundary.js +61 -0
- package/ClientErrorBoundary.js.map +7 -0
- package/DocumentReadyBeacon.d.ts +1 -0
- package/DocumentReadyBeacon.js +9 -0
- package/DocumentReadyBeacon.js.map +7 -0
- package/LICENSE +11 -0
- package/LinkInterceptor.d.ts +1 -0
- package/LinkInterceptor.js +128 -0
- package/LinkInterceptor.js.map +7 -0
- package/NextErrorReporter.d.ts +1 -0
- package/NextErrorReporter.js +92 -0
- package/NextErrorReporter.js.map +7 -0
- package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
- package/_vendor/replo-utils/lib/errors.d.ts +83 -0
- package/_vendor/replo-utils/lib/fonts.d.ts +49 -0
- package/_vendor/replo-utils/lib/fonts.mjs +221 -0
- package/_vendor/replo-utils/lib/geometry.d.ts +18 -0
- package/_vendor/replo-utils/lib/geometry.mjs +9 -0
- package/_vendor/replo-utils/lib/json.d.ts +6 -0
- package/_vendor/replo-utils/lib/log.d.ts +11 -0
- package/_vendor/replo-utils/lib/misc.d.ts +145 -0
- package/_vendor/replo-utils/lib/misc.mjs +2 -0
- package/_vendor/replo-utils/lib/object.d.ts +31 -0
- package/_vendor/replo-utils/lib/parse.d.ts +15 -0
- package/_vendor/replo-utils/lib/posthogReplay.d.ts +43 -0
- package/_vendor/replo-utils/lib/posthogReplay.mjs +121 -0
- package/_vendor/replo-utils/react/use-iframe-rpc-channel.d.ts +14 -0
- package/_vendor/replo-utils/react/use-iframe-rpc-channel.mjs +14 -0
- package/_vendor/schemas/designLibrary.d.ts +361 -0
- package/_vendor/schemas/designLibrary.mjs +126 -0
- package/_vendor/schemas/errors.d.ts +459 -0
- package/_vendor/schemas/generated/errors.d.ts +61 -0
- package/_vendor/schemas/generated/pageAnnotations.d.ts +48 -0
- package/_vendor/schemas/generated/pageAnnotations.mjs +1 -0
- package/_vendor/schemas/generated/selectMode.d.ts +119 -0
- package/_vendor/schemas/generated/selectMode.mjs +1 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +100 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.mjs +1 -0
- package/_vendor/schemas/iframeRpcChannel.d.ts +82 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +520 -0
- package/_vendor/schemas/pageAnnotations.d.ts +134 -0
- package/_vendor/schemas/pageAnnotations.mjs +61 -0
- package/_vendor/schemas/savedStyles.d.ts +140 -0
- package/_vendor/schemas/selectMode.d.ts +522 -0
- package/_vendor/schemas/selectMode.mjs +3 -0
- package/_vendor/schemas/selectModeIframeRpc.d.ts +900 -0
- package/_vendor/schemas/selectModeIframeRpc.mjs +223 -0
- package/annotations/AnnotationLayerMount.d.ts +1 -0
- package/annotations/AnnotationLayerMount.js +13 -0
- package/annotations/AnnotationLayerMount.js.map +7 -0
- package/annotations/annotationController.d.ts +1 -0
- package/annotations/annotationController.js +384 -0
- package/annotations/annotationController.js.map +7 -0
- package/annotations/bridgeGate.d.ts +7 -0
- package/annotations/bridgeGate.js +22 -0
- package/annotations/bridgeGate.js.map +7 -0
- package/chunk-3UFG7XTQ.mjs +205 -0
- package/chunk-FNTGY7I5.mjs +68 -0
- package/chunk-N5EI2MOZ.mjs +1535 -0
- package/design-library-preview/DesignLibraryPreview.d.ts +1 -0
- package/design-library-preview/DesignLibraryPreview.js +93 -0
- package/design-library-preview/DesignLibraryPreview.js.map +7 -0
- package/package.json +41 -0
- package/posthog-relay.d.ts +11 -0
- package/posthog-relay.js +72 -0
- package/posthog-relay.js.map +7 -0
- package/sandbox-runtime.d.ts +1 -0
- package/sandbox-runtime.js +29 -0
- package/sandbox-runtime.js.map +7 -0
- package/select-mode/EditorSelection.d.ts +24 -0
- package/select-mode/EditorSelection.js +95 -0
- package/select-mode/EditorSelection.js.map +7 -0
- package/select-mode/annotationFrames.d.ts +33 -0
- package/select-mode/annotationFrames.js +119 -0
- package/select-mode/annotationFrames.js.map +7 -0
- package/select-mode/descriptor/elementDescriptor.d.ts +26 -0
- package/select-mode/descriptor/elementDescriptor.js +201 -0
- package/select-mode/descriptor/elementDescriptor.js.map +7 -0
- package/select-mode/descriptor/styleExtraction.d.ts +3 -0
- package/select-mode/descriptor/styleExtraction.js +271 -0
- package/select-mode/descriptor/styleExtraction.js.map +7 -0
- package/select-mode/displayModeStore.d.ts +10 -0
- package/select-mode/displayModeStore.js +47 -0
- package/select-mode/displayModeStore.js.map +7 -0
- package/select-mode/fast-refresh-relay.d.ts +3 -0
- package/select-mode/fast-refresh-relay.js +33 -0
- package/select-mode/fast-refresh-relay.js.map +7 -0
- package/select-mode/fiber.d.ts +49 -0
- package/select-mode/fiber.js +306 -0
- package/select-mode/fiber.js.map +7 -0
- package/select-mode/hooks/useCrosshairCursor.d.ts +12 -0
- package/select-mode/hooks/useCrosshairCursor.js +20 -0
- package/select-mode/hooks/useCrosshairCursor.js.map +7 -0
- package/select-mode/hooks/useEditModeStateMachine.d.ts +16 -0
- package/select-mode/hooks/useEditModeStateMachine.js +235 -0
- package/select-mode/hooks/useEditModeStateMachine.js.map +7 -0
- package/select-mode/hooks/useOptimisticOverrides.d.ts +17 -0
- package/select-mode/hooks/useOptimisticOverrides.js +283 -0
- package/select-mode/hooks/useOptimisticOverrides.js.map +7 -0
- package/select-mode/hooks/useParentMessaging.d.ts +36 -0
- package/select-mode/hooks/useParentMessaging.js +74 -0
- package/select-mode/hooks/useParentMessaging.js.map +7 -0
- package/select-mode/hooks/useSelectModeController.d.ts +8 -0
- package/select-mode/hooks/useSelectModeController.js +46 -0
- package/select-mode/hooks/useSelectModeController.js.map +7 -0
- package/select-mode/hooks/useSelectionHover.d.ts +22 -0
- package/select-mode/hooks/useSelectionHover.js +110 -0
- package/select-mode/hooks/useSelectionHover.js.map +7 -0
- package/select-mode/hooks/useSelectionInteractions.d.ts +37 -0
- package/select-mode/hooks/useSelectionInteractions.js +353 -0
- package/select-mode/hooks/useSelectionInteractions.js.map +7 -0
- package/select-mode/hooks/useSelectionScrollSync.d.ts +28 -0
- package/select-mode/hooks/useSelectionScrollSync.js +70 -0
- package/select-mode/hooks/useSelectionScrollSync.js.map +7 -0
- package/select-mode/hooks/useSiblingResolution.d.ts +6 -0
- package/select-mode/hooks/useSiblingResolution.js +59 -0
- package/select-mode/hooks/useSiblingResolution.js.map +7 -0
- package/select-mode/iframeRpcChannel.d.ts +356 -0
- package/select-mode/iframeRpcChannel.js +92 -0
- package/select-mode/iframeRpcChannel.js.map +7 -0
- package/select-mode/inlineTextMarks.d.ts +6 -0
- package/select-mode/inlineTextMarks.js +25 -0
- package/select-mode/inlineTextMarks.js.map +7 -0
- package/select-mode/interactions/selectionGeometry.d.ts +105 -0
- package/select-mode/interactions/selectionGeometry.js +192 -0
- package/select-mode/interactions/selectionGeometry.js.map +7 -0
- package/select-mode/selectModeSession.d.ts +21 -0
- package/select-mode/selectModeSession.js +7 -0
- package/select-mode/selectModeSession.js.map +7 -0
- package/select-mode/selectionFrame.d.ts +81 -0
- package/select-mode/selectionFrame.js +132 -0
- package/select-mode/selectionFrame.js.map +7 -0
- package/select-mode/symbolicate.d.ts +2 -0
- package/select-mode/symbolicate.js +200 -0
- package/select-mode/symbolicate.js.map +7 -0
- package/select-mode/tiptapInlineEditor.d.ts +23 -0
- package/select-mode/tiptapInlineEditor.js +120 -0
- package/select-mode/tiptapInlineEditor.js.map +7 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function DesignLibraryPreview(): null;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { buildGoogleFontsCssUrl } from "../_vendor/replo-utils/lib/fonts.mjs";
|
|
4
|
+
import { designLibraryPreviewMessageSchema } from "../_vendor/schemas/designLibrary.mjs";
|
|
5
|
+
const FONT_LINK_DATA_KEY = "reploDesignLibraryFont";
|
|
6
|
+
function escapeCssString(value) {
|
|
7
|
+
return value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'");
|
|
8
|
+
}
|
|
9
|
+
function DesignLibraryPreview() {
|
|
10
|
+
const ownedKeys = useRef(null);
|
|
11
|
+
const ownedFontLinks = useRef(null);
|
|
12
|
+
const ownedFontFaces = useRef(null);
|
|
13
|
+
const ownsDefaultFont = useRef(false);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleMessage = (event) => {
|
|
16
|
+
const parsed = designLibraryPreviewMessageSchema.safeParse(event.data);
|
|
17
|
+
if (!parsed.success) return;
|
|
18
|
+
const { cssVars, fonts, defaultFontCssVar } = parsed.data;
|
|
19
|
+
const root = document.documentElement;
|
|
20
|
+
const nextVars = { ...cssVars };
|
|
21
|
+
for (const font of fonts) {
|
|
22
|
+
nextVars[font.cssVar] = `'${escapeCssString(font.family)}'`;
|
|
23
|
+
}
|
|
24
|
+
const owned = ownedKeys.current ?? /* @__PURE__ */ new Set();
|
|
25
|
+
for (const key of owned) {
|
|
26
|
+
if (!(key in nextVars)) root.style.removeProperty(key);
|
|
27
|
+
}
|
|
28
|
+
ownedKeys.current = new Set(Object.keys(nextVars));
|
|
29
|
+
for (const [key, value] of Object.entries(nextVars)) {
|
|
30
|
+
root.style.setProperty(key, value);
|
|
31
|
+
}
|
|
32
|
+
const customFonts = fonts.filter((font) => Boolean(font.faceUrl));
|
|
33
|
+
const googleFonts = fonts.filter((font) => !font.faceUrl);
|
|
34
|
+
const links = ownedFontLinks.current ?? /* @__PURE__ */ new Map();
|
|
35
|
+
const nextHrefs = new Set(
|
|
36
|
+
googleFonts.map((font) => buildGoogleFontsCssUrl(font.family))
|
|
37
|
+
);
|
|
38
|
+
for (const [href, link] of links) {
|
|
39
|
+
if (!nextHrefs.has(href)) {
|
|
40
|
+
link.remove();
|
|
41
|
+
links.delete(href);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const href of nextHrefs) {
|
|
45
|
+
if (links.has(href)) continue;
|
|
46
|
+
const link = document.createElement("link");
|
|
47
|
+
link.rel = "stylesheet";
|
|
48
|
+
link.href = href;
|
|
49
|
+
link.dataset[FONT_LINK_DATA_KEY] = "";
|
|
50
|
+
document.head.append(link);
|
|
51
|
+
links.set(href, link);
|
|
52
|
+
}
|
|
53
|
+
ownedFontLinks.current = links;
|
|
54
|
+
const faces = ownedFontFaces.current ?? /* @__PURE__ */ new Map();
|
|
55
|
+
const nextFamilies = new Set(customFonts.map((font) => font.family));
|
|
56
|
+
for (const [family, entry] of faces) {
|
|
57
|
+
if (!nextFamilies.has(family)) {
|
|
58
|
+
entry.style.remove();
|
|
59
|
+
faces.delete(family);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const font of customFonts) {
|
|
63
|
+
if (!font.faceUrl) continue;
|
|
64
|
+
const existing = faces.get(font.family);
|
|
65
|
+
if (existing?.url === font.faceUrl) continue;
|
|
66
|
+
existing?.style.remove();
|
|
67
|
+
const style = document.createElement("style");
|
|
68
|
+
style.dataset[FONT_LINK_DATA_KEY] = "";
|
|
69
|
+
const format = font.format ? ` format('${escapeCssString(font.format)}')` : "";
|
|
70
|
+
style.textContent = `@font-face{font-family:'${escapeCssString(font.family)}';font-weight:400;font-style:normal;src:url('${escapeCssString(font.faceUrl)}')${format};font-display:swap;}`;
|
|
71
|
+
document.head.append(style);
|
|
72
|
+
faces.set(font.family, { url: font.faceUrl, style });
|
|
73
|
+
}
|
|
74
|
+
ownedFontFaces.current = faces;
|
|
75
|
+
if (defaultFontCssVar) {
|
|
76
|
+
root.style.setProperty("font-family", `var(${defaultFontCssVar})`);
|
|
77
|
+
ownsDefaultFont.current = true;
|
|
78
|
+
} else if (ownsDefaultFont.current) {
|
|
79
|
+
root.style.removeProperty("font-family");
|
|
80
|
+
ownsDefaultFont.current = false;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
window.addEventListener("message", handleMessage);
|
|
84
|
+
return () => {
|
|
85
|
+
window.removeEventListener("message", handleMessage);
|
|
86
|
+
};
|
|
87
|
+
}, []);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
DesignLibraryPreview
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=DesignLibraryPreview.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../design-library-preview/DesignLibraryPreview.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nimport { buildGoogleFontsCssUrl } from \"replo-utils/lib/fonts\";\nimport { designLibraryPreviewMessageSchema } from \"schemas/designLibrary\";\n\nconst FONT_LINK_DATA_KEY = \"reploDesignLibraryFont\";\n\nfunction escapeCssString(value: string): string {\n return value.replace(/\\\\/gu, \"\\\\\\\\\").replace(/'/gu, \"\\\\'\");\n}\n\nexport function DesignLibraryPreview() {\n // NOTE (Ben, 2026-06-03): The custom-property keys we set last time, so on the\n // next message we remove only the ones we own that the new override set drops.\n const ownedKeys = useRef<Set<string> | null>(null);\n // NOTE (Ben, 2026-06-05): The Google Font <link> hrefs we injected, keyed by\n // href so we de-dupe and remove only our own when the override set changes.\n const ownedFontLinks = useRef<Map<string, HTMLLinkElement> | null>(null);\n // NOTE (Ben, 2026-06-05): The @font-face <style> nodes for uploaded custom\n // fonts, keyed by family with the URL we injected, so we de-dupe, re-inject\n // when a family's asset URL changes (re-upload), and remove only our own when\n // the override set changes.\n const ownedFontFaces = useRef<Map<\n string,\n { url: string; style: HTMLStyleElement }\n > | null>(null);\n // NOTE (Ben, 2026-06-05): Whether we set the document default font-family, so\n // we can clear just that.\n const ownsDefaultFont = useRef(false);\n\n // eslint-disable-next-line replo/no-use-effect -- Subscribes to window message events from the parent editor.\n useEffect(() => {\n const handleMessage = (event: MessageEvent) => {\n const parsed = designLibraryPreviewMessageSchema.safeParse(event.data);\n if (!parsed.success) return;\n const { cssVars, fonts, defaultFontCssVar } = parsed.data;\n const root = document.documentElement;\n\n // --- CSS variable overrides (colors + font role vars) ---\n const nextVars: Record<string, string> = { ...cssVars };\n for (const font of fonts) {\n nextVars[font.cssVar] = `'${escapeCssString(font.family)}'`;\n }\n const owned = ownedKeys.current ?? new Set();\n for (const key of owned) {\n if (!(key in nextVars)) root.style.removeProperty(key);\n }\n ownedKeys.current = new Set(Object.keys(nextVars));\n for (const [key, value] of Object.entries(nextVars)) {\n root.style.setProperty(key, value);\n }\n\n // Custom uploaded fonts carry a faceUrl; Google fonts don't.\n const customFonts = fonts.filter((font) => Boolean(font.faceUrl));\n const googleFonts = fonts.filter((font) => !font.faceUrl);\n\n // --- Google Font stylesheets (<link> to the CDN) ---\n const links = ownedFontLinks.current ?? new Map();\n const nextHrefs = new Set(\n googleFonts.map((font) => buildGoogleFontsCssUrl(font.family)),\n );\n for (const [href, link] of links) {\n if (!nextHrefs.has(href)) {\n link.remove();\n links.delete(href);\n }\n }\n for (const href of nextHrefs) {\n if (links.has(href)) continue;\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n link.dataset[FONT_LINK_DATA_KEY] = \"\";\n document.head.append(link);\n links.set(href, link);\n }\n ownedFontLinks.current = links;\n\n // --- Custom uploaded fonts (@font-face from the asset URL) ---\n const faces = ownedFontFaces.current ?? new Map();\n const nextFamilies = new Set(customFonts.map((font) => font.family));\n for (const [family, entry] of faces) {\n if (!nextFamilies.has(family)) {\n entry.style.remove();\n faces.delete(family);\n }\n }\n for (const font of customFonts) {\n if (!font.faceUrl) continue;\n const existing = faces.get(font.family);\n // Re-upload reuses the family but changes the asset URL; replace the\n // stale node rather than skipping it.\n if (existing?.url === font.faceUrl) continue;\n existing?.style.remove();\n const style = document.createElement(\"style\");\n style.dataset[FONT_LINK_DATA_KEY] = \"\";\n const format = font.format\n ? ` format('${escapeCssString(font.format)}')`\n : \"\";\n style.textContent = `@font-face{font-family:'${escapeCssString(font.family)}';font-weight:400;font-style:normal;src:url('${escapeCssString(font.faceUrl)}')${format};font-display:swap;}`;\n document.head.append(style);\n faces.set(font.family, { url: font.faceUrl, style });\n }\n ownedFontFaces.current = faces;\n\n // --- Document default font ---\n if (defaultFontCssVar) {\n root.style.setProperty(\"font-family\", `var(${defaultFontCssVar})`);\n ownsDefaultFont.current = true;\n } else if (ownsDefaultFont.current) {\n root.style.removeProperty(\"font-family\");\n ownsDefaultFont.current = false;\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@replohq/sandbox-runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./sandbox-runtime.d.ts",
|
|
11
|
+
"default": "./sandbox-runtime.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@tiptap/core": "^2.25.0",
|
|
17
|
+
"@tiptap/extension-bold": "^2.25.0",
|
|
18
|
+
"@tiptap/extension-document": "^2.25.0",
|
|
19
|
+
"@tiptap/extension-italic": "^2.25.0",
|
|
20
|
+
"@tiptap/extension-strike": "^2.25.0",
|
|
21
|
+
"@tiptap/extension-text": "^2.25.0",
|
|
22
|
+
"@tiptap/extension-underline": "^2.25.0",
|
|
23
|
+
"@tiptap/pm": "^2.25.0",
|
|
24
|
+
"uuid": "^11.1.0",
|
|
25
|
+
"zod": "^4.3.6"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@types/react": ">=18.2.0",
|
|
29
|
+
"@types/react-dom": ">=18.2.0",
|
|
30
|
+
"next": ">=15.0.0",
|
|
31
|
+
"react": ">=18.2.0",
|
|
32
|
+
"react-dom": ">=18.2.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"registry": "https://registry.npmjs.org/",
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": "22.x"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface PostHog {
|
|
2
|
+
init: (apiKey: string, config: Record<string, unknown>) => void;
|
|
3
|
+
sessionRecordingStarted: () => boolean;
|
|
4
|
+
}
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
posthog?: PostHog;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export default function PostHogRelay(): null;
|
|
11
|
+
export {};
|
package/posthog-relay.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { startChildPostHogReplaySnapshotRelay } from "./_vendor/replo-utils/lib/posthogReplay.mjs";
|
|
4
|
+
const POSTHOG_API_KEY = "phc_dRETsj85vNkKrvQQszEFRBibH5kUF9l7rbjACuKPanZ";
|
|
5
|
+
const POSTHOG_API_HOST = "https://d99bbtllqsaif.cloudfront.net";
|
|
6
|
+
let initPromise = null;
|
|
7
|
+
function loadPostHogScript() {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
if (window.posthog) {
|
|
10
|
+
resolve();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const script = document.createElement("script");
|
|
14
|
+
script.src = `${POSTHOG_API_HOST}/static/array.js`;
|
|
15
|
+
script.async = true;
|
|
16
|
+
script.addEventListener("load", () => resolve());
|
|
17
|
+
script.addEventListener("error", () => {
|
|
18
|
+
resolve();
|
|
19
|
+
console.warn("[PostHog Relay] Failed to load PostHog script");
|
|
20
|
+
});
|
|
21
|
+
document.head.append(script);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function initPostHogRelay() {
|
|
25
|
+
if (window.self === window.top) {
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
if (initPromise) {
|
|
29
|
+
return initPromise;
|
|
30
|
+
}
|
|
31
|
+
initPromise = (async () => {
|
|
32
|
+
await loadPostHogScript();
|
|
33
|
+
if (!window.posthog) {
|
|
34
|
+
initPromise = null;
|
|
35
|
+
console.warn("[PostHog Relay] PostHog not available after script load");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
window.posthog.init(POSTHOG_API_KEY, {
|
|
39
|
+
api_host: POSTHOG_API_HOST,
|
|
40
|
+
persistence: "localStorage",
|
|
41
|
+
autocapture: false,
|
|
42
|
+
// (Noah, 2026-07-14, REPL-29305): Disable performance capture — its PerformanceObserver
|
|
43
|
+
// relays a non-cloneable PerformanceServerTiming across the iframe boundary via postMessage,
|
|
44
|
+
// throwing a DataCloneError in the preview. Matches the bedrock-sdk relay.
|
|
45
|
+
capture_performance: false,
|
|
46
|
+
capture_pageview: false,
|
|
47
|
+
session_recording: {
|
|
48
|
+
recordCrossOriginIframes: true,
|
|
49
|
+
// NOTE (Nick, 2026-02-02, REPL-23284): Disable canvas recording to prevent SecurityError on tainted canvases
|
|
50
|
+
captureCanvas: { recordCanvas: false }
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
})();
|
|
54
|
+
return initPromise;
|
|
55
|
+
}
|
|
56
|
+
function PostHogRelay() {
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const stopRelayingSnapshotRequests = startChildPostHogReplaySnapshotRelay({
|
|
59
|
+
isRecordingStarted: () => {
|
|
60
|
+
return window.posthog?.sessionRecordingStarted() ?? false;
|
|
61
|
+
},
|
|
62
|
+
targetWindow: window
|
|
63
|
+
});
|
|
64
|
+
void initPostHogRelay();
|
|
65
|
+
return stopRelayingSnapshotRequests;
|
|
66
|
+
}, []);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
PostHogRelay as default
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=posthog-relay.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../posthog-relay.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\n/**\n * PostHog Cross-Origin Iframe Relay\n *\n * Initializes PostHog inside the sandbox iframe as a \"relay\" - it captures DOM\n * mutations via rrweb and sends them to the parent window's PostHog instance\n * via postMessage, enabling session recordings to include iframe content.\n */\nimport { useEffect } from \"react\";\n\nimport { startChildPostHogReplaySnapshotRelay } from \"replo-utils/lib/posthogReplay\";\n\nconst POSTHOG_API_KEY = \"phc_dRETsj85vNkKrvQQszEFRBibH5kUF9l7rbjACuKPanZ\";\nconst POSTHOG_API_HOST = \"https://d99bbtllqsaif.cloudfront.net\";\n\ninterface PostHog {\n init: (apiKey: string, config: Record<string, unknown>) => void;\n sessionRecordingStarted: () => boolean;\n}\n\ndeclare global {\n interface Window {\n posthog?: PostHog;\n }\n}\n\nlet initPromise: Promise<void> | null = null;\n\nfunction loadPostHogScript() {\n return new Promise<void>((resolve) => {\n if (window.posthog) {\n resolve();\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = `${POSTHOG_API_HOST}/static/array.js`;\n script.async = true;\n script.addEventListener(\"load\", () => resolve());\n script.addEventListener(\"error\", () => {\n resolve();\n console.warn(\"[PostHog Relay] Failed to load PostHog script\");\n });\n document.head.append(script);\n });\n}\n\nfunction initPostHogRelay() {\n if (window.self === window.top) {\n return Promise.resolve();\n }\n\n if (initPromise) {\n return initPromise;\n }\n\n initPromise = (async () => {\n await loadPostHogScript();\n\n if (!window.posthog) {\n initPromise = null;\n console.warn(\"[PostHog Relay] PostHog not available after script load\");\n return;\n }\n\n window.posthog.init(POSTHOG_API_KEY, {\n api_host: POSTHOG_API_HOST,\n persistence: \"localStorage\",\n autocapture: false,\n // (Noah, 2026-07-14, REPL-29305): Disable performance capture \u2014 its PerformanceObserver\n // relays a non-cloneable PerformanceServerTiming across the iframe boundary via postMessage,\n // throwing a DataCloneError in the preview. Matches the bedrock-sdk relay.\n capture_performance: false,\n capture_pageview: false,\n session_recording: {\n recordCrossOriginIframes: true,\n // NOTE (Nick, 2026-02-02, REPL-23284): Disable canvas recording to prevent SecurityError on tainted canvases\n captureCanvas: { recordCanvas: false },\n },\n });\n })();\n\n return initPromise;\n}\n\nexport default function PostHogRelay() {\n // eslint-disable-next-line replo/no-use-effect -- External replay script and cross-frame listener lifecycle.\n useEffect(() => {\n const stopRelayingSnapshotRequests = startChildPostHogReplaySnapshotRelay({\n isRecordingStarted: () => {\n return window.posthog?.sessionRecordingStarted() ?? false;\n },\n targetWindow: window,\n });\n void initPostHogRelay();\n return stopRelayingSnapshotRequests;\n }, []);\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": ";AASA,SAAS,iBAAiB;AAE1B,SAAS,4CAA4C;AAErD,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAazB,IAAI,cAAoC;AAExC,SAAS,oBAAoB;AAC3B,SAAO,IAAI,QAAc,CAAC,YAAY;AACpC,QAAI,OAAO,SAAS;AAClB,cAAQ;AACR;AAAA,IACF;AAEA,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,MAAM,GAAG,gBAAgB;AAChC,WAAO,QAAQ;AACf,WAAO,iBAAiB,QAAQ,MAAM,QAAQ,CAAC;AAC/C,WAAO,iBAAiB,SAAS,MAAM;AACrC,cAAQ;AACR,cAAQ,KAAK,+CAA+C;AAAA,IAC9D,CAAC;AACD,aAAS,KAAK,OAAO,MAAM;AAAA,EAC7B,CAAC;AACH;AAEA,SAAS,mBAAmB;AAC1B,MAAI,OAAO,SAAS,OAAO,KAAK;AAC9B,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAEA,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,iBAAe,YAAY;AACzB,UAAM,kBAAkB;AAExB,QAAI,CAAC,OAAO,SAAS;AACnB,oBAAc;AACd,cAAQ,KAAK,yDAAyD;AACtE;AAAA,IACF;AAEA,WAAO,QAAQ,KAAK,iBAAiB;AAAA,MACnC,UAAU;AAAA,MACV,aAAa;AAAA,MACb,aAAa;AAAA;AAAA;AAAA;AAAA,MAIb,qBAAqB;AAAA,MACrB,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,QACjB,0BAA0B;AAAA;AAAA,QAE1B,eAAe,EAAE,cAAc,MAAM;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,EACH,GAAG;AAEH,SAAO;AACT;AAEe,SAAR,eAAgC;AAErC,YAAU,MAAM;AACd,UAAM,+BAA+B,qCAAqC;AAAA,MACxE,oBAAoB,MAAM;AACxB,eAAO,OAAO,SAAS,wBAAwB,KAAK;AAAA,MACtD;AAAA,MACA,cAAc;AAAA,IAChB,CAAC;AACD,SAAK,iBAAiB;AACtB,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SandboxRuntime({ children }: React.PropsWithChildren): string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AnnotationLayerMount } from "./annotations/AnnotationLayerMount";
|
|
4
|
+
import { ClientErrorBoundary } from "./ClientErrorBoundary";
|
|
5
|
+
import { DesignLibraryPreview } from "./design-library-preview/DesignLibraryPreview";
|
|
6
|
+
import { DocumentReadyBeacon } from "./DocumentReadyBeacon";
|
|
7
|
+
import LinkInterceptor from "./LinkInterceptor";
|
|
8
|
+
import { NextErrorReporter } from "./NextErrorReporter";
|
|
9
|
+
import PostHogRelay from "./posthog-relay";
|
|
10
|
+
import { EditorSelection } from "./select-mode/EditorSelection";
|
|
11
|
+
function SandboxRuntime({ children }) {
|
|
12
|
+
const isDevServer = process.env.NODE_ENV === "development";
|
|
13
|
+
return isDevServer ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14
|
+
/* @__PURE__ */ jsx(DocumentReadyBeacon, {}),
|
|
15
|
+
/* @__PURE__ */ jsx(NextErrorReporter, {}),
|
|
16
|
+
/* @__PURE__ */ jsxs(ClientErrorBoundary, { children: [
|
|
17
|
+
/* @__PURE__ */ jsx(LinkInterceptor, {}),
|
|
18
|
+
/* @__PURE__ */ jsx(PostHogRelay, {}),
|
|
19
|
+
/* @__PURE__ */ jsx(EditorSelection, {}),
|
|
20
|
+
/* @__PURE__ */ jsx(AnnotationLayerMount, {}),
|
|
21
|
+
/* @__PURE__ */ jsx(DesignLibraryPreview, {}),
|
|
22
|
+
children
|
|
23
|
+
] })
|
|
24
|
+
] }) : children;
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
SandboxRuntime
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=sandbox-runtime.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../sandbox-runtime.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport { AnnotationLayerMount } from \"./annotations/AnnotationLayerMount\";\nimport { ClientErrorBoundary } from \"./ClientErrorBoundary\";\nimport { DesignLibraryPreview } from \"./design-library-preview/DesignLibraryPreview\";\nimport { DocumentReadyBeacon } from \"./DocumentReadyBeacon\";\nimport LinkInterceptor from \"./LinkInterceptor\";\nimport { NextErrorReporter } from \"./NextErrorReporter\";\nimport PostHogRelay from \"./posthog-relay\";\nimport { EditorSelection } from \"./select-mode/EditorSelection\";\n\nexport function SandboxRuntime({ children }: React.PropsWithChildren) {\n // The editor only embeds dev servers (`next dev`), so editor-integration components render there only \u2014 production builds ship none of them and behave like plain Next apps.\n const isDevServer = process.env.NODE_ENV === \"development\";\n\n return isDevServer ? (\n <>\n <DocumentReadyBeacon />\n {/* NOTE (Max, 2026-06-08): Outside ClientErrorBoundary on purpose \u2014 when\n the boundary catches a runtime error it swaps its children for a\n neutral cover, which would unmount the reporter and freeze it in the\n error state. It must stay mounted to observe Next's overlay across\n error <-> recover. */}\n <NextErrorReporter />\n <ClientErrorBoundary>\n <LinkInterceptor />\n <PostHogRelay />\n <EditorSelection />\n <AnnotationLayerMount />\n <DesignLibraryPreview />\n {children}\n </ClientErrorBoundary>\n </>\n ) : (\n children\n );\n}\n"],
|
|
5
|
+
"mappings": ";AAgBI,mBACE,KAOA,YARF;AAdJ,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,OAAO,qBAAqB;AAC5B,SAAS,yBAAyB;AAClC,OAAO,kBAAkB;AACzB,SAAS,uBAAuB;AAEzB,SAAS,eAAe,EAAE,SAAS,GAA4B;AAEpE,QAAM,cAAc,QAAQ,IAAI,aAAa;AAE7C,SAAO,cACL,iCACE;AAAA,wBAAC,uBAAoB;AAAA,IAMrB,oBAAC,qBAAkB;AAAA,IACnB,qBAAC,uBACC;AAAA,0BAAC,mBAAgB;AAAA,MACjB,oBAAC,gBAAa;AAAA,MACd,oBAAC,mBAAgB;AAAA,MACjB,oBAAC,wBAAqB;AAAA,MACtB,oBAAC,wBAAqB;AAAA,MACrB;AAAA,OACH;AAAA,KACF,IAEA;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mounts the iframe-side selectMode controller. This is a render-less
|
|
3
|
+
* component — every responsibility is delegated to a dedicated hook so each
|
|
4
|
+
* concern (parent messaging, cursor styling, pointer interactions, hover
|
|
5
|
+
* tracking, scroll syncing) can be read in isolation:
|
|
6
|
+
*
|
|
7
|
+
* - `useParentMessaging` — inbound messages from the parent (set mode,
|
|
8
|
+
* set selection type) + outbound `IFRAME_READY` handshake.
|
|
9
|
+
* - `useCrosshairCursor` — crosshair cursor + disable text/drag selection
|
|
10
|
+
* while in selection mode.
|
|
11
|
+
* - `useSelectionInteractions` — pointer-down / move / up state machine
|
|
12
|
+
* that produces `ELEMENT_SELECTED`, `AREA_SELECTED`, and live
|
|
13
|
+
* `DRAG_RECT_UPDATED` messages.
|
|
14
|
+
* - `useSelectionHover` — hover-element rect updates.
|
|
15
|
+
* - `useSelectionScrollSync` — keeps the parent overlay aligned with the
|
|
16
|
+
* selected element/area when the iframe scrolls.
|
|
17
|
+
*
|
|
18
|
+
* The `interactionSessionRef` is the single source of truth for the
|
|
19
|
+
* in-progress drag/selection — every hook reads/writes it via ref so we
|
|
20
|
+
* don't trigger React re-renders during interaction-heavy operations.
|
|
21
|
+
*
|
|
22
|
+
* @author Kevin O'Connell 2026-04-17
|
|
23
|
+
*/
|
|
24
|
+
export declare function EditorSelection(): null;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect } from "react";
|
|
3
|
+
import { buildElementDescriptor } from "./descriptor/elementDescriptor";
|
|
4
|
+
import { installFastRefreshRelay } from "./fast-refresh-relay";
|
|
5
|
+
import { useCrosshairCursor } from "./hooks/useCrosshairCursor";
|
|
6
|
+
import { useEditModeStateMachine } from "./hooks/useEditModeStateMachine";
|
|
7
|
+
import { useOptimisticOverrides } from "./hooks/useOptimisticOverrides";
|
|
8
|
+
import { useParentMessaging } from "./hooks/useParentMessaging";
|
|
9
|
+
import { useSelectionHover } from "./hooks/useSelectionHover";
|
|
10
|
+
import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
|
|
11
|
+
import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
12
|
+
import { useSelectModeController } from "./hooks/useSelectModeController";
|
|
13
|
+
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
14
|
+
import { symbolicateSelectedElement } from "./symbolicate";
|
|
15
|
+
function EditorSelection() {
|
|
16
|
+
const {
|
|
17
|
+
displayMode,
|
|
18
|
+
interactionSessionRef,
|
|
19
|
+
setDisplayMode,
|
|
20
|
+
setSelectionType
|
|
21
|
+
} = useSelectModeController();
|
|
22
|
+
const { beginEditing, editingElementRef, formatCommand, revert } = useEditModeStateMachine({
|
|
23
|
+
displayMode,
|
|
24
|
+
interactionSessionRef
|
|
25
|
+
});
|
|
26
|
+
const {
|
|
27
|
+
applyOptimisticUpdate,
|
|
28
|
+
markCommitted,
|
|
29
|
+
reapplyOverrides,
|
|
30
|
+
resolveSessionOverrides,
|
|
31
|
+
revertOverride
|
|
32
|
+
} = useOptimisticOverrides();
|
|
33
|
+
const postSelectedElementSnapshot = useCallback(() => {
|
|
34
|
+
const session = interactionSessionRef.current;
|
|
35
|
+
if (session.type !== "element") {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const selectedElement = session.data.kind === "react-element" ? document.querySelector(session.data.selector) : null;
|
|
39
|
+
const element = selectedElement ?? session.element;
|
|
40
|
+
if (!(element instanceof Element) || !element.isConnected) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const descriptor = buildElementDescriptor({
|
|
44
|
+
element,
|
|
45
|
+
clickPosition: session.clickPosition ?? (session.data.kind === "react-element" ? session.data.clickPosition : { x: 0, y: 0 }),
|
|
46
|
+
siblingSelectors: session.data.kind === "react-element" ? session.data.siblingSelectors : []
|
|
47
|
+
});
|
|
48
|
+
if (descriptor.kind !== "react-element") {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
interactionSessionRef.current = {
|
|
52
|
+
...session,
|
|
53
|
+
element,
|
|
54
|
+
data: descriptor
|
|
55
|
+
};
|
|
56
|
+
void symbolicateSelectedElement(descriptor).then((symbolicated) => {
|
|
57
|
+
getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });
|
|
58
|
+
});
|
|
59
|
+
}, [interactionSessionRef]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
installFastRefreshRelay({
|
|
62
|
+
onComplete: () => {
|
|
63
|
+
reapplyOverrides({ skipElement: editingElementRef.current });
|
|
64
|
+
postSelectedElementSnapshot();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);
|
|
68
|
+
useParentMessaging({
|
|
69
|
+
applyOptimisticUpdate,
|
|
70
|
+
markCommitted,
|
|
71
|
+
onExitEditMode: revert,
|
|
72
|
+
onFormatCommand: formatCommand,
|
|
73
|
+
resolveSessionOverrides,
|
|
74
|
+
revertOverride,
|
|
75
|
+
setDisplayMode,
|
|
76
|
+
setSelectionType
|
|
77
|
+
});
|
|
78
|
+
useCrosshairCursor({ displayMode });
|
|
79
|
+
useSelectionInteractions({
|
|
80
|
+
displayMode,
|
|
81
|
+
editingElementRef,
|
|
82
|
+
interactionSessionRef,
|
|
83
|
+
onBeginEditing: beginEditing
|
|
84
|
+
});
|
|
85
|
+
useSelectionHover({ displayMode, interactionSessionRef });
|
|
86
|
+
useSelectionScrollSync({
|
|
87
|
+
displayMode,
|
|
88
|
+
interactionSessionRef
|
|
89
|
+
});
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
EditorSelection
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=EditorSelection.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../select-mode/EditorSelection.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport { buildElementDescriptor } from \"./descriptor/elementDescriptor\";\nimport { installFastRefreshRelay } from \"./fast-refresh-relay\";\nimport { useCrosshairCursor } from \"./hooks/useCrosshairCursor\";\nimport { useEditModeStateMachine } from \"./hooks/useEditModeStateMachine\";\nimport { useOptimisticOverrides } from \"./hooks/useOptimisticOverrides\";\nimport { useParentMessaging } from \"./hooks/useParentMessaging\";\nimport { useSelectionHover } from \"./hooks/useSelectionHover\";\nimport { useSelectionInteractions } from \"./hooks/useSelectionInteractions\";\nimport { useSelectionScrollSync } from \"./hooks/useSelectionScrollSync\";\nimport { useSelectModeController } from \"./hooks/useSelectModeController\";\nimport { getWebsiteBuilderRpc } from \"./iframeRpcChannel\";\nimport { symbolicateSelectedElement } from \"./symbolicate\";\n\n/**\n * Mounts the iframe-side selectMode controller. This is a render-less\n * component \u2014 every responsibility is delegated to a dedicated hook so each\n * concern (parent messaging, cursor styling, pointer interactions, hover\n * tracking, scroll syncing) can be read in isolation:\n *\n * - `useParentMessaging` \u2014 inbound messages from the parent (set mode,\n * set selection type) + outbound `IFRAME_READY` handshake.\n * - `useCrosshairCursor` \u2014 crosshair cursor + disable text/drag selection\n * while in selection mode.\n * - `useSelectionInteractions` \u2014 pointer-down / move / up state machine\n * that produces `ELEMENT_SELECTED`, `AREA_SELECTED`, and live\n * `DRAG_RECT_UPDATED` messages.\n * - `useSelectionHover` \u2014 hover-element rect updates.\n * - `useSelectionScrollSync` \u2014 keeps the parent overlay aligned with the\n * selected element/area when the iframe scrolls.\n *\n * The `interactionSessionRef` is the single source of truth for the\n * in-progress drag/selection \u2014 every hook reads/writes it via ref so we\n * don't trigger React re-renders during interaction-heavy operations.\n *\n * @author Kevin O'Connell 2026-04-17\n */\nexport function EditorSelection() {\n const {\n displayMode,\n interactionSessionRef,\n setDisplayMode,\n setSelectionType,\n } = useSelectModeController();\n const { beginEditing, editingElementRef, formatCommand, revert } =\n useEditModeStateMachine({\n displayMode,\n interactionSessionRef,\n });\n const {\n applyOptimisticUpdate,\n markCommitted,\n reapplyOverrides,\n resolveSessionOverrides,\n revertOverride,\n } = useOptimisticOverrides();\n\n const postSelectedElementSnapshot = useCallback(() => {\n const session = interactionSessionRef.current;\n if (session.type !== \"element\") {\n return;\n }\n\n const selectedElement =\n session.data.kind === \"react-element\"\n ? document.querySelector(session.data.selector)\n : null;\n const element = selectedElement ?? session.element;\n if (!(element instanceof Element) || !element.isConnected) {\n return;\n }\n\n const descriptor = buildElementDescriptor({\n element,\n clickPosition:\n session.clickPosition ??\n (session.data.kind === \"react-element\"\n ? session.data.clickPosition\n : { x: 0, y: 0 }),\n siblingSelectors:\n session.data.kind === \"react-element\"\n ? session.data.siblingSelectors\n : [],\n });\n if (descriptor.kind !== \"react-element\") {\n return;\n }\n\n interactionSessionRef.current = {\n ...session,\n element,\n data: descriptor,\n };\n\n void symbolicateSelectedElement(descriptor).then((symbolicated) => {\n getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });\n });\n }, [interactionSessionRef]);\n\n // eslint-disable-next-line replo/no-use-effect -- Installs a window-level console bridge for the preview iframe.\n useEffect(() => {\n installFastRefreshRelay({\n onComplete: () => {\n // Re-apply before re-symbolicating so the snapshot reads the\n // optimistic content, not the source the refresh just repainted.\n reapplyOverrides({ skipElement: editingElementRef.current });\n postSelectedElementSnapshot();\n },\n });\n }, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);\n\n useParentMessaging({\n applyOptimisticUpdate,\n markCommitted,\n onExitEditMode: revert,\n onFormatCommand: formatCommand,\n resolveSessionOverrides,\n revertOverride,\n setDisplayMode,\n setSelectionType,\n });\n useCrosshairCursor({ displayMode });\n useSelectionInteractions({\n displayMode,\n editingElementRef,\n interactionSessionRef,\n onBeginEditing: beginEditing,\n });\n useSelectionHover({ displayMode, interactionSessionRef });\n useSelectionScrollSync({\n displayMode,\n interactionSessionRef,\n });\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAS,aAAa,iBAAiB;AAEvC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,kBAAkB;AAChC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,wBAAwB;AAC5B,QAAM,EAAE,cAAc,mBAAmB,eAAe,OAAO,IAC7D,wBAAwB;AAAA,IACtB;AAAA,IACA;AAAA,EACF,CAAC;AACH,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,uBAAuB;AAE3B,QAAM,8BAA8B,YAAY,MAAM;AACpD,UAAM,UAAU,sBAAsB;AACtC,QAAI,QAAQ,SAAS,WAAW;AAC9B;AAAA,IACF;AAEA,UAAM,kBACJ,QAAQ,KAAK,SAAS,kBAClB,SAAS,cAAc,QAAQ,KAAK,QAAQ,IAC5C;AACN,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,QAAI,EAAE,mBAAmB,YAAY,CAAC,QAAQ,aAAa;AACzD;AAAA,IACF;AAEA,UAAM,aAAa,uBAAuB;AAAA,MACxC;AAAA,MACA,eACE,QAAQ,kBACP,QAAQ,KAAK,SAAS,kBACnB,QAAQ,KAAK,gBACb,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACnB,kBACE,QAAQ,KAAK,SAAS,kBAClB,QAAQ,KAAK,mBACb,CAAC;AAAA,IACT,CAAC;AACD,QAAI,WAAW,SAAS,iBAAiB;AACvC;AAAA,IACF;AAEA,0BAAsB,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH;AAAA,MACA,MAAM;AAAA,IACR;AAEA,SAAK,2BAA2B,UAAU,EAAE,KAAK,CAAC,iBAAiB;AACjE,2BAAqB,GAAG,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAAA,IAChE,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,CAAC;AAG1B,YAAU,MAAM;AACd,4BAAwB;AAAA,MACtB,YAAY,MAAM;AAGhB,yBAAiB,EAAE,aAAa,kBAAkB,QAAQ,CAAC;AAC3D,oCAA4B;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,mBAAmB,6BAA6B,gBAAgB,CAAC;AAErE,qBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,qBAAmB,EAAE,YAAY,CAAC;AAClC,2BAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,CAAC;AACD,oBAAkB,EAAE,aAAa,sBAAsB,CAAC;AACxD,yBAAuB;AAAA,IACrB;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const ANNOTATION_CONTAINER_ID = "replo-annotation-frames";
|
|
2
|
+
interface AnnotationRect {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface AnnotationFrameSpec {
|
|
9
|
+
id: string;
|
|
10
|
+
pageRect: AnnotationRect;
|
|
11
|
+
badgeCenter: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
positioning: "absolute" | "fixed";
|
|
16
|
+
colorHex: string;
|
|
17
|
+
badgeLabel: string;
|
|
18
|
+
badgeAriaLabel: string;
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
onBadgeClick: () => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Style-only selection update: rebuilding the node pool mid-interaction would
|
|
24
|
+
* race the pointer sequence and swallow the click, so the active state never
|
|
25
|
+
* re-renders — it just retargets transforms on the existing nodes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setActiveAnnotationBadge(id: string | null): void;
|
|
28
|
+
export declare function getAnnotationBadgeViewportRect(id: string): AnnotationRect | null;
|
|
29
|
+
export declare function showAnnotationFrames({ frames, }: {
|
|
30
|
+
frames: AnnotationFrameSpec[];
|
|
31
|
+
}): void;
|
|
32
|
+
export declare function clearAnnotationFrames(): void;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { getOrCreateFramesLayerContainer } from "./selectionFrame";
|
|
2
|
+
const ANNOTATION_CONTAINER_ID = "replo-annotation-frames";
|
|
3
|
+
const ANNOTATION_FONT_STYLE_ID = "replo-annotation-font";
|
|
4
|
+
const BADGE_SIZE_PX = 30;
|
|
5
|
+
const EDITORIAL_ITALIC_FONT_URL = "https://assets.replocdn.com/PPEditorialNew-Italic.woff2";
|
|
6
|
+
const BADGE_FONT = 'italic 400 16px/1 "PP Editorial New", Georgia, "Times New Roman", serif';
|
|
7
|
+
function ensureBadgeFont() {
|
|
8
|
+
if (document.getElementById(ANNOTATION_FONT_STYLE_ID)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const style = document.createElement("style");
|
|
12
|
+
style.id = ANNOTATION_FONT_STYLE_ID;
|
|
13
|
+
style.textContent = `@font-face{font-family:"PP Editorial New";src:url("${EDITORIAL_ITALIC_FONT_URL}") format("woff2");font-weight:normal;font-style:italic;font-display:swap;}`;
|
|
14
|
+
document.head.append(style);
|
|
15
|
+
}
|
|
16
|
+
const badgeNodesById = /* @__PURE__ */ new Map();
|
|
17
|
+
let activeBadgeId = null;
|
|
18
|
+
function badgeRestingTransform(id) {
|
|
19
|
+
return id === activeBadgeId ? "scale(1.15)" : "scale(1)";
|
|
20
|
+
}
|
|
21
|
+
function setActiveAnnotationBadge(id) {
|
|
22
|
+
activeBadgeId = id;
|
|
23
|
+
for (const [badgeId, badge] of badgeNodesById) {
|
|
24
|
+
badge.style.transform = badgeRestingTransform(badgeId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getAnnotationBadgeViewportRect(id) {
|
|
28
|
+
const badge = badgeNodesById.get(id);
|
|
29
|
+
if (!badge?.isConnected) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const rect = badge.getBoundingClientRect();
|
|
33
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
34
|
+
}
|
|
35
|
+
function createFrameNode(spec) {
|
|
36
|
+
const frame = document.createElement("div");
|
|
37
|
+
frame.style.cssText = [
|
|
38
|
+
`position: ${spec.positioning}`,
|
|
39
|
+
`left: ${spec.pageRect.x}px`,
|
|
40
|
+
`top: ${spec.pageRect.y}px`,
|
|
41
|
+
"width: 0",
|
|
42
|
+
"height: 0",
|
|
43
|
+
"pointer-events: none",
|
|
44
|
+
"margin: 0",
|
|
45
|
+
"padding: 0"
|
|
46
|
+
].join(";");
|
|
47
|
+
const badge = document.createElement("button");
|
|
48
|
+
badge.type = "button";
|
|
49
|
+
badge.setAttribute("aria-label", spec.badgeAriaLabel);
|
|
50
|
+
badge.textContent = spec.badgeLabel;
|
|
51
|
+
badge.style.cssText = [
|
|
52
|
+
"position: absolute",
|
|
53
|
+
`left: ${spec.badgeCenter.x - spec.pageRect.x - BADGE_SIZE_PX / 2}px`,
|
|
54
|
+
`top: ${spec.badgeCenter.y - spec.pageRect.y - BADGE_SIZE_PX / 2}px`,
|
|
55
|
+
`width: ${BADGE_SIZE_PX}px`,
|
|
56
|
+
`height: ${BADGE_SIZE_PX}px`,
|
|
57
|
+
"display: flex",
|
|
58
|
+
"align-items: center",
|
|
59
|
+
"justify-content: center",
|
|
60
|
+
"border: none",
|
|
61
|
+
"border-radius: 50%",
|
|
62
|
+
`background: ${spec.colorHex}`,
|
|
63
|
+
"color: #ffffff",
|
|
64
|
+
`font: ${BADGE_FONT}`,
|
|
65
|
+
"box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.9), 0 1px 3px rgba(0, 0, 0, 0.18)",
|
|
66
|
+
`transform: scale(${spec.isActive ? 1.15 : 1})`,
|
|
67
|
+
"transition: transform 120ms ease-out",
|
|
68
|
+
"cursor: pointer",
|
|
69
|
+
"pointer-events: auto",
|
|
70
|
+
"margin: 0",
|
|
71
|
+
"padding: 0 0 1px 0"
|
|
72
|
+
].join(";");
|
|
73
|
+
badge.addEventListener("pointerdown", () => {
|
|
74
|
+
badge.style.transform = "scale(0.9)";
|
|
75
|
+
});
|
|
76
|
+
badge.addEventListener("pointerup", () => {
|
|
77
|
+
badge.style.transform = badgeRestingTransform(spec.id);
|
|
78
|
+
});
|
|
79
|
+
badge.addEventListener("pointerleave", () => {
|
|
80
|
+
badge.style.transform = badgeRestingTransform(spec.id);
|
|
81
|
+
});
|
|
82
|
+
badge.addEventListener("click", (event) => {
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
spec.onBadgeClick();
|
|
85
|
+
});
|
|
86
|
+
frame.append(badge);
|
|
87
|
+
badgeNodesById.set(spec.id, badge);
|
|
88
|
+
return frame;
|
|
89
|
+
}
|
|
90
|
+
function showAnnotationFrames({
|
|
91
|
+
frames
|
|
92
|
+
}) {
|
|
93
|
+
const container = getOrCreateFramesLayerContainer({
|
|
94
|
+
id: ANNOTATION_CONTAINER_ID,
|
|
95
|
+
zIndex: 2147483645
|
|
96
|
+
});
|
|
97
|
+
if (!container) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
ensureBadgeFont();
|
|
101
|
+
activeBadgeId = frames.find((spec) => spec.isActive)?.id ?? null;
|
|
102
|
+
badgeNodesById.clear();
|
|
103
|
+
container.replaceChildren(...frames.map((spec) => createFrameNode(spec)));
|
|
104
|
+
}
|
|
105
|
+
function clearAnnotationFrames() {
|
|
106
|
+
badgeNodesById.clear();
|
|
107
|
+
if (typeof document === "undefined") {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
document.getElementById(ANNOTATION_CONTAINER_ID)?.replaceChildren();
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
ANNOTATION_CONTAINER_ID,
|
|
114
|
+
clearAnnotationFrames,
|
|
115
|
+
getAnnotationBadgeViewportRect,
|
|
116
|
+
setActiveAnnotationBadge,
|
|
117
|
+
showAnnotationFrames
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=annotationFrames.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../select-mode/annotationFrames.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * In-iframe annotation frame renderer \u2014 `selectionFrame`'s sibling surface.\n *\n * Same architecture and reasons as selection frames (see selectionFrame.ts):\n * page-coordinate `position: absolute` nodes in a body-level container, so\n * document scroll translates them with zero JavaScript per scroll frame, and\n * styling is vanilla inline CSS because customer pages have no tailwind.\n *\n * An annotation frame is an invisible positioning box over the annotated\n * element carrying a numbered badge dot \u2014 the dot is the entire visual.\n * Unlike selection frames these rebuild wholesale on each call: re-renders\n * happen on set/re-measure (rare), never per scroll, so pooling buys nothing.\n */\nimport { getOrCreateFramesLayerContainer } from \"./selectionFrame\";\n\nexport const ANNOTATION_CONTAINER_ID = \"replo-annotation-frames\";\nconst ANNOTATION_FONT_STYLE_ID = \"replo-annotation-font\";\nconst BADGE_SIZE_PX = 30;\n// The badge numerals use the same PP Editorial the editor brand uses, served\n// from the CDN asset the editor already loads \u2014 no font bytes ship in canopy,\n// and this layer only exists in dev previews. `swap` degrades to Georgia.\nconst EDITORIAL_ITALIC_FONT_URL =\n \"https://assets.replocdn.com/PPEditorialNew-Italic.woff2\";\nconst BADGE_FONT =\n 'italic 400 16px/1 \"PP Editorial New\", Georgia, \"Times New Roman\", serif';\n\nfunction ensureBadgeFont() {\n if (document.getElementById(ANNOTATION_FONT_STYLE_ID)) {\n return;\n }\n const style = document.createElement(\"style\");\n style.id = ANNOTATION_FONT_STYLE_ID;\n style.textContent = `@font-face{font-family:\"PP Editorial New\";src:url(\"${EDITORIAL_ITALIC_FONT_URL}\") format(\"woff2\");font-weight:normal;font-style:italic;font-display:swap;}`;\n document.head.append(style);\n}\n\ninterface AnnotationRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AnnotationFrameSpec {\n id: string;\n pageRect: AnnotationRect;\n badgeCenter: { x: number; y: number };\n // \"fixed\" for elements inside fixed/sticky ancestors: those don't translate\n // on scroll, so their frame must not either (coords are viewport coords).\n positioning: \"absolute\" | \"fixed\";\n colorHex: string;\n badgeLabel: string;\n badgeAriaLabel: string;\n isActive: boolean;\n onBadgeClick: () => void;\n}\n\nconst badgeNodesById = new Map<string, HTMLButtonElement>();\nlet activeBadgeId: string | null = null;\n\nfunction badgeRestingTransform(id: string): string {\n return id === activeBadgeId ? \"scale(1.15)\" : \"scale(1)\";\n}\n\n/**\n * Style-only selection update: rebuilding the node pool mid-interaction would\n * race the pointer sequence and swallow the click, so the active state never\n * re-renders \u2014 it just retargets transforms on the existing nodes.\n */\nexport function setActiveAnnotationBadge(id: string | null) {\n activeBadgeId = id;\n for (const [badgeId, badge] of badgeNodesById) {\n badge.style.transform = badgeRestingTransform(badgeId);\n }\n}\n\nexport function getAnnotationBadgeViewportRect(\n id: string,\n): AnnotationRect | null {\n const badge = badgeNodesById.get(id);\n if (!badge?.isConnected) {\n return null;\n }\n const rect = badge.getBoundingClientRect();\n return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n}\n\nfunction createFrameNode(spec: AnnotationFrameSpec): HTMLDivElement {\n const frame = document.createElement(\"div\");\n frame.style.cssText = [\n `position: ${spec.positioning}`,\n `left: ${spec.pageRect.x}px`,\n `top: ${spec.pageRect.y}px`,\n \"width: 0\",\n \"height: 0\",\n \"pointer-events: none\",\n \"margin: 0\",\n \"padding: 0\",\n ].join(\";\");\n\n const badge = document.createElement(\"button\");\n badge.type = \"button\";\n badge.setAttribute(\"aria-label\", spec.badgeAriaLabel);\n badge.textContent = spec.badgeLabel;\n badge.style.cssText = [\n \"position: absolute\",\n `left: ${spec.badgeCenter.x - spec.pageRect.x - BADGE_SIZE_PX / 2}px`,\n `top: ${spec.badgeCenter.y - spec.pageRect.y - BADGE_SIZE_PX / 2}px`,\n `width: ${BADGE_SIZE_PX}px`,\n `height: ${BADGE_SIZE_PX}px`,\n \"display: flex\",\n \"align-items: center\",\n \"justify-content: center\",\n \"border: none\",\n \"border-radius: 50%\",\n `background: ${spec.colorHex}`,\n \"color: #ffffff\",\n `font: ${BADGE_FONT}`,\n \"box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.9), 0 1px 3px rgba(0, 0, 0, 0.18)\",\n `transform: scale(${spec.isActive ? 1.15 : 1})`,\n \"transition: transform 120ms ease-out\",\n \"cursor: pointer\",\n \"pointer-events: auto\",\n \"margin: 0\",\n \"padding: 0 0 1px 0\",\n ].join(\";\");\n badge.addEventListener(\"pointerdown\", () => {\n badge.style.transform = \"scale(0.9)\";\n });\n badge.addEventListener(\"pointerup\", () => {\n badge.style.transform = badgeRestingTransform(spec.id);\n });\n badge.addEventListener(\"pointerleave\", () => {\n badge.style.transform = badgeRestingTransform(spec.id);\n });\n badge.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n spec.onBadgeClick();\n });\n\n frame.append(badge);\n badgeNodesById.set(spec.id, badge);\n return frame;\n}\n\nexport function showAnnotationFrames({\n frames,\n}: {\n frames: AnnotationFrameSpec[];\n}) {\n const container = getOrCreateFramesLayerContainer({\n id: ANNOTATION_CONTAINER_ID,\n zIndex: 2147483645,\n });\n if (!container) {\n return;\n }\n ensureBadgeFont();\n activeBadgeId = frames.find((spec) => spec.isActive)?.id ?? null;\n badgeNodesById.clear();\n container.replaceChildren(...frames.map((spec) => createFrameNode(spec)));\n}\n\nexport function clearAnnotationFrames() {\n badgeNodesById.clear();\n if (typeof document === \"undefined\") {\n return;\n }\n document.getElementById(ANNOTATION_CONTAINER_ID)?.replaceChildren();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|