@replohq/sandbox-runtime 0.3.0 → 1.0.1
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/_vendor/schemas/generated/selectModeIframeRpc.d.ts +8 -0
- package/_vendor/schemas/selectModeIframeRpc.d.ts +21 -1
- package/_vendor/schemas/selectModeIframeRpc.mjs +11 -3
- package/buildMetadata.d.ts +9 -0
- package/buildMetadata.js +7 -0
- package/buildMetadata.js.map +7 -0
- package/package.json +17 -5
- package/select-mode/EditorSelection.js +8 -2
- package/select-mode/EditorSelection.js.map +2 -2
- package/select-mode/fiber.d.ts +12 -17
- package/select-mode/fiber.js +228 -64
- package/select-mode/fiber.js.map +2 -2
- package/select-mode/hooks/useEditModeStateMachine.js +2 -0
- package/select-mode/hooks/useEditModeStateMachine.js.map +2 -2
- package/select-mode/hooks/useOptimisticOverrides.js +2 -2
- package/select-mode/hooks/useOptimisticOverrides.js.map +2 -2
- package/select-mode/hooks/useParentMessaging.js +21 -1
- package/select-mode/hooks/useParentMessaging.js.map +2 -2
- package/select-mode/hooks/useSelectModeController.js +2 -0
- package/select-mode/hooks/useSelectModeController.js.map +2 -2
- package/select-mode/hooks/useSelectionHover.d.ts +4 -5
- package/select-mode/hooks/useSelectionHover.js +21 -3
- package/select-mode/hooks/useSelectionHover.js.map +2 -2
- package/select-mode/hooks/useSelectionInteractions.js +12 -11
- package/select-mode/hooks/useSelectionInteractions.js.map +2 -2
- package/select-mode/hooks/useSelectionScrollSync.d.ts +3 -4
- package/select-mode/hooks/useSelectionScrollSync.js +4 -9
- package/select-mode/hooks/useSelectionScrollSync.js.map +2 -2
- package/select-mode/hooks/useSiblingResolution.d.ts +2 -2
- package/select-mode/hooks/useSiblingResolution.js +10 -47
- package/select-mode/hooks/useSiblingResolution.js.map +2 -2
- package/select-mode/iframeRpcChannel.d.ts +7 -1
- package/select-mode/selectModeSession.d.ts +1 -0
- package/select-mode/selectModeSession.js.map +2 -2
- package/select-mode/selectionFrame.d.ts +13 -17
- package/select-mode/selectionFrame.js +18 -5
- package/select-mode/selectionFrame.js.map +2 -2
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { DisplayMode, SelectModeSelectionType, SelectModeOptimisticUpdatePayload, SelectModeFormatCommand, SelectableData, SelectModeBoundingRect, SelectModeClickPosition, SelectModeSelectionRect, SelectModeTiptapActiveMarks } from "./selectMode";
|
|
2
|
+
export type IframeRuntimeBuildMetadata = {
|
|
3
|
+
packageName: string;
|
|
4
|
+
version: string;
|
|
5
|
+
branch: string | null;
|
|
6
|
+
commit: string | null;
|
|
7
|
+
builtAt: string | null;
|
|
8
|
+
};
|
|
2
9
|
export type SelectModeParentMessage = {
|
|
3
10
|
method: "setDisplayMode";
|
|
4
11
|
data: {
|
|
@@ -46,6 +53,7 @@ export type SelectModeParentMessage = {
|
|
|
46
53
|
};
|
|
47
54
|
export type SelectModeIframeMessage = {
|
|
48
55
|
method: "iframeReady";
|
|
56
|
+
data?: IframeRuntimeBuildMetadata;
|
|
49
57
|
} | {
|
|
50
58
|
method: "elementSelected";
|
|
51
59
|
data: {
|
|
@@ -2,6 +2,13 @@ import type { SelectModeIframeMessage, SelectModeParentMessage } from "./generat
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
export declare const SELECT_MODE_IFRAME_RPC_CHANNEL_ID: "replo:select-mode-iframe-rpc";
|
|
4
4
|
export declare const SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION: 1;
|
|
5
|
+
export declare const iframeRuntimeBuildMetadataSchema: z.ZodObject<{
|
|
6
|
+
packageName: z.ZodString;
|
|
7
|
+
version: z.ZodString;
|
|
8
|
+
branch: z.ZodNullable<z.ZodString>;
|
|
9
|
+
commit: z.ZodNullable<z.ZodString>;
|
|
10
|
+
builtAt: z.ZodNullable<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
5
12
|
export declare const websiteBuilderToCanopyMethodConfig: {
|
|
6
13
|
symbolicateSelectedElement: {
|
|
7
14
|
inputSchema: z.ZodObject<{
|
|
@@ -280,7 +287,13 @@ export declare const canopyToWebsiteBuilderMethodConfig: {
|
|
|
280
287
|
timeoutMs: number;
|
|
281
288
|
};
|
|
282
289
|
iframeReady: {
|
|
283
|
-
inputSchema: z.
|
|
290
|
+
inputSchema: z.ZodOptional<z.ZodObject<{
|
|
291
|
+
packageName: z.ZodString;
|
|
292
|
+
version: z.ZodString;
|
|
293
|
+
branch: z.ZodNullable<z.ZodString>;
|
|
294
|
+
commit: z.ZodNullable<z.ZodString>;
|
|
295
|
+
builtAt: z.ZodNullable<z.ZodString>;
|
|
296
|
+
}, z.core.$strip>>;
|
|
284
297
|
outputSchema: z.ZodVoid;
|
|
285
298
|
};
|
|
286
299
|
elementSelected: {
|
|
@@ -629,6 +642,13 @@ export declare const selectModeParentMessageSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
629
642
|
}, z.core.$strip>], "method">;
|
|
630
643
|
export declare const selectModeIframeMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
631
644
|
method: z.ZodLiteral<"iframeReady">;
|
|
645
|
+
data: z.ZodOptional<z.ZodObject<{
|
|
646
|
+
packageName: z.ZodString;
|
|
647
|
+
version: z.ZodString;
|
|
648
|
+
branch: z.ZodNullable<z.ZodString>;
|
|
649
|
+
commit: z.ZodNullable<z.ZodString>;
|
|
650
|
+
builtAt: z.ZodNullable<z.ZodString>;
|
|
651
|
+
}, z.core.$strip>>;
|
|
632
652
|
}, z.core.$strip>, z.ZodObject<{
|
|
633
653
|
method: z.ZodLiteral<"elementSelected">;
|
|
634
654
|
data: z.ZodObject<{
|
|
@@ -5,6 +5,13 @@ import { z } from 'zod';
|
|
|
5
5
|
|
|
6
6
|
var SELECT_MODE_IFRAME_RPC_CHANNEL_ID = "replo:select-mode-iframe-rpc";
|
|
7
7
|
var SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION = 1;
|
|
8
|
+
var iframeRuntimeBuildMetadataSchema = z.object({
|
|
9
|
+
packageName: z.string(),
|
|
10
|
+
version: z.string(),
|
|
11
|
+
branch: z.string().nullable(),
|
|
12
|
+
commit: z.string().nullable(),
|
|
13
|
+
builtAt: z.string().nullable()
|
|
14
|
+
}).describe("IframeRuntimeBuildMetadata");
|
|
8
15
|
var websiteBuilderToCanopyMethodConfig = {
|
|
9
16
|
symbolicateSelectedElement: {
|
|
10
17
|
inputSchema: selectedElementSchema,
|
|
@@ -78,7 +85,7 @@ var canopyToWebsiteBuilderMethodConfig = {
|
|
|
78
85
|
timeoutMs: 5e3
|
|
79
86
|
},
|
|
80
87
|
iframeReady: {
|
|
81
|
-
inputSchema:
|
|
88
|
+
inputSchema: iframeRuntimeBuildMetadataSchema.optional(),
|
|
82
89
|
outputSchema: z.void()
|
|
83
90
|
},
|
|
84
91
|
elementSelected: {
|
|
@@ -181,7 +188,8 @@ var selectModeParentMessageSchema = z.discriminatedUnion("method", [
|
|
|
181
188
|
]).describe("SelectModeParentMessage");
|
|
182
189
|
var selectModeIframeMessageSchema = z.discriminatedUnion("method", [
|
|
183
190
|
z.object({
|
|
184
|
-
method: z.literal("iframeReady")
|
|
191
|
+
method: z.literal("iframeReady"),
|
|
192
|
+
data: iframeRuntimeBuildMetadataSchema.optional()
|
|
185
193
|
}),
|
|
186
194
|
z.object({
|
|
187
195
|
method: z.literal("elementSelected"),
|
|
@@ -220,4 +228,4 @@ var selectModeIframeMessageSchema = z.discriminatedUnion("method", [
|
|
|
220
228
|
})
|
|
221
229
|
]).describe("SelectModeIframeMessage");
|
|
222
230
|
|
|
223
|
-
export { SELECT_MODE_IFRAME_RPC_CHANNEL_ID, SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION, canopyToWebsiteBuilderMethodConfig, selectModeIframeMessageSchema, selectModeParentMessageSchema, websiteBuilderToCanopyMethodConfig };
|
|
231
|
+
export { SELECT_MODE_IFRAME_RPC_CHANNEL_ID, SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION, canopyToWebsiteBuilderMethodConfig, iframeRuntimeBuildMetadataSchema, selectModeIframeMessageSchema, selectModeParentMessageSchema, websiteBuilderToCanopyMethodConfig };
|
package/buildMetadata.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../buildMetadata.ts"],
|
|
4
|
+
"sourcesContent": ["type BuildMetadata = {\n packageName: string;\n version: string;\n branch: string | null;\n commit: string | null;\n builtAt: string | null;\n};\n\nexport const BUILD_METADATA: BuildMetadata = {\n packageName: \"@replohq/sandbox-runtime\",\n version: \"0.0.0-dev\",\n branch: null,\n commit: null,\n builtAt: null,\n};\n"],
|
|
5
|
+
"mappings": "AAQO,MAAM,iBAAgC;AAAA,EAC3C,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"reploBuild": {
|
|
5
|
+
"packageName": "@replohq/sandbox-runtime",
|
|
6
|
+
"version": "1.0.1",
|
|
7
|
+
"branch": "HEAD",
|
|
8
|
+
"commit": "9fc57a3ef1d06689e70fe0d90c25c03717e37d03",
|
|
9
|
+
"builtAt": "2026-08-19T22:39:33.469Z"
|
|
10
|
+
},
|
|
4
11
|
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
5
12
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
13
|
"type": "module",
|
|
@@ -33,11 +40,16 @@
|
|
|
33
40
|
"zod": "^4.3.6"
|
|
34
41
|
},
|
|
35
42
|
"peerDependencies": {
|
|
36
|
-
"@types/react": ">=
|
|
37
|
-
"@types/react-dom": ">=
|
|
43
|
+
"@types/react": ">=19.0.0",
|
|
44
|
+
"@types/react-dom": ">=19.0.0",
|
|
38
45
|
"next": ">=15.0.0",
|
|
39
|
-
"react": ">=
|
|
40
|
-
"react-dom": ">=
|
|
46
|
+
"react": ">=19.0.0",
|
|
47
|
+
"react-dom": ">=19.0.0"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/replohq/andytown.git",
|
|
52
|
+
"directory": "packages/sandbox-runtime"
|
|
41
53
|
},
|
|
42
54
|
"publishConfig": {
|
|
43
55
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useEffect } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildElementDescriptor,
|
|
5
|
+
buildSelector
|
|
6
|
+
} from "./descriptor/elementDescriptor";
|
|
4
7
|
import { installFastRefreshRelay } from "./fast-refresh-relay";
|
|
5
8
|
import { useCrosshairCursor } from "./hooks/useCrosshairCursor";
|
|
6
9
|
import { useEditModeStateMachine } from "./hooks/useEditModeStateMachine";
|
|
@@ -10,6 +13,7 @@ import { useSelectionHover } from "./hooks/useSelectionHover";
|
|
|
10
13
|
import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
|
|
11
14
|
import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
12
15
|
import { useSelectModeController } from "./hooks/useSelectModeController";
|
|
16
|
+
import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
|
|
13
17
|
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
14
18
|
import { symbolicateSelectedElement } from "./symbolicate";
|
|
15
19
|
function EditorSelection() {
|
|
@@ -40,10 +44,11 @@ function EditorSelection() {
|
|
|
40
44
|
if (!(element instanceof Element) || !element.isConnected) {
|
|
41
45
|
return;
|
|
42
46
|
}
|
|
47
|
+
const siblingElements = getRepetitionSiblingElements({ element });
|
|
43
48
|
const descriptor = buildElementDescriptor({
|
|
44
49
|
element,
|
|
45
50
|
clickPosition: session.clickPosition ?? (session.data.kind === "react-element" ? session.data.clickPosition : { x: 0, y: 0 }),
|
|
46
|
-
siblingSelectors:
|
|
51
|
+
siblingSelectors: siblingElements.map(buildSelector)
|
|
47
52
|
});
|
|
48
53
|
if (descriptor.kind !== "react-element") {
|
|
49
54
|
return;
|
|
@@ -51,6 +56,7 @@ function EditorSelection() {
|
|
|
51
56
|
interactionSessionRef.current = {
|
|
52
57
|
...session,
|
|
53
58
|
element,
|
|
59
|
+
siblingElements,
|
|
54
60
|
data: descriptor
|
|
55
61
|
};
|
|
56
62
|
void symbolicateSelectedElement(descriptor).then((symbolicated) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../select-mode/EditorSelection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport {
|
|
5
|
-
"mappings": ";AAEA,SAAS,aAAa,iBAAiB;AAEvC,
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport {\n buildElementDescriptor,\n buildSelector,\n} 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 { getRepetitionSiblingElements } from \"./hooks/useSiblingResolution\";\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 // A fast refresh can replace the repeated DOM nodes, so the group is\n // re-resolved from the (possibly re-created) element rather than carried\n // over from the stale session.\n const siblingElements = getRepetitionSiblingElements({ element });\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: siblingElements.map(buildSelector),\n });\n if (descriptor.kind !== \"react-element\") {\n return;\n }\n\n interactionSessionRef.current = {\n ...session,\n element,\n siblingElements,\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;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,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,oCAAoC;AAC7C,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;AAKA,UAAM,kBAAkB,6BAA6B,EAAE,QAAQ,CAAC;AAChE,UAAM,aAAa,uBAAuB;AAAA,MACxC;AAAA,MACA,eACE,QAAQ,kBACP,QAAQ,KAAK,SAAS,kBACnB,QAAQ,KAAK,gBACb,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACnB,kBAAkB,gBAAgB,IAAI,aAAa;AAAA,IACrD,CAAC;AACD,QAAI,WAAW,SAAS,iBAAiB;AACvC;AAAA,IACF;AAEA,0BAAsB,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH;AAAA,MACA;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
6
|
"names": []
|
|
7
7
|
}
|
package/select-mode/fiber.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
export interface DebugSource {
|
|
2
|
+
fileName: string;
|
|
3
|
+
lineNumber: number;
|
|
4
|
+
columnNumber?: number;
|
|
5
|
+
}
|
|
1
6
|
export interface ReactFiber {
|
|
2
|
-
_debugSource?: {
|
|
3
|
-
fileName: string;
|
|
4
|
-
lineNumber: number;
|
|
5
|
-
columnNumber?: number;
|
|
6
|
-
};
|
|
7
7
|
_debugStack?: unknown;
|
|
8
8
|
_debugOwner?: ReactFiber;
|
|
9
9
|
type?: {
|
|
@@ -11,17 +11,17 @@ export interface ReactFiber {
|
|
|
11
11
|
displayName?: string;
|
|
12
12
|
} | string | ((...args: unknown[]) => unknown) | null;
|
|
13
13
|
return?: ReactFiber | null;
|
|
14
|
+
child?: ReactFiber | null;
|
|
15
|
+
sibling?: ReactFiber | null;
|
|
16
|
+
alternate?: ReactFiber | null;
|
|
17
|
+
stateNode?: unknown;
|
|
14
18
|
memoizedProps?: Record<string, unknown>;
|
|
15
19
|
pendingProps?: Record<string, unknown>;
|
|
16
20
|
}
|
|
17
21
|
export declare function getFiberFromElement(element: HTMLElement): ReactFiber | null;
|
|
18
22
|
export declare function getComponentName(fiber: ReactFiber | null | undefined): string | undefined;
|
|
19
|
-
export declare function findDebugSource(fiber: ReactFiber
|
|
20
|
-
source:
|
|
21
|
-
componentName?: string;
|
|
22
|
-
} | null;
|
|
23
|
-
export declare function findDebugSourceReact19(fiber: ReactFiber): {
|
|
24
|
-
source: ReactFiber["_debugSource"];
|
|
23
|
+
export declare function findDebugSource(fiber: ReactFiber): {
|
|
24
|
+
source: DebugSource;
|
|
25
25
|
componentName?: string;
|
|
26
26
|
} | null;
|
|
27
27
|
export declare function getSourceLocation(element: HTMLElement): {
|
|
@@ -30,12 +30,7 @@ export declare function getSourceLocation(element: HTMLElement): {
|
|
|
30
30
|
columnNumber: number | undefined;
|
|
31
31
|
componentName: string | undefined;
|
|
32
32
|
} | undefined;
|
|
33
|
-
export declare function
|
|
34
|
-
filePath: string;
|
|
35
|
-
lineNumber: number;
|
|
36
|
-
columnNumber: number | undefined;
|
|
37
|
-
tagName: string;
|
|
38
|
-
} | null;
|
|
33
|
+
export declare function getRepetitionHostNodes(element: HTMLElement): HTMLElement[];
|
|
39
34
|
export declare function getFiberChain(element: HTMLElement): {
|
|
40
35
|
sourceLocation: {
|
|
41
36
|
filePath: string;
|
package/select-mode/fiber.js
CHANGED
|
@@ -2,6 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
const MAX_FIBER_CHAIN_DEPTH = 80;
|
|
3
3
|
const MAX_SERIALIZED_PROP_COUNT = 24;
|
|
4
4
|
const MAX_SERIALIZED_STRING_LENGTH = 500;
|
|
5
|
+
const MAX_SIBLING_SCAN = 5e3;
|
|
6
|
+
const MAX_SUBTREE_SCAN = 5e3;
|
|
7
|
+
const MAX_ROOT_CLIMB_DEPTH = 1e3;
|
|
8
|
+
const MAX_CURRENT_TREE_SCAN = 5e4;
|
|
5
9
|
const REACT_INTERNAL_FRAME_PATTERNS = [
|
|
6
10
|
/react.*jsx-dev-runtime/,
|
|
7
11
|
/react.*jsx-runtime/,
|
|
@@ -20,9 +24,9 @@ function isReactInternalFrame(fileName) {
|
|
|
20
24
|
const REACT_INTERNAL_METHOD_PATTERNS = [
|
|
21
25
|
/(?:^|\.)jsxDEV(?:Impl)?$/,
|
|
22
26
|
/(?:^|\.)jsxs?$/,
|
|
23
|
-
/(?:^|\.)react_stack_bottom_frame$/,
|
|
24
27
|
/(?:^|\.)fakeJSXCallSite$/
|
|
25
28
|
];
|
|
29
|
+
const REACT_STACK_BOTTOM_FRAME_PATTERN = /react[-_]stack[-_]bottom[-_]frame/;
|
|
26
30
|
function isReactInternalMethod(methodName) {
|
|
27
31
|
if (!methodName) {
|
|
28
32
|
return false;
|
|
@@ -49,7 +53,12 @@ function parseDebugStackSource(debugStack) {
|
|
|
49
53
|
if (!line) {
|
|
50
54
|
continue;
|
|
51
55
|
}
|
|
52
|
-
|
|
56
|
+
if (REACT_STACK_BOTTOM_FRAME_PATTERN.test(line)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const framedMatch = line.match(/^at\s+(.+?)\s+\((.*):(\d+):(\d+)\)$/) ?? // Method names never contain `/`, which keeps `@`-versioned path
|
|
60
|
+
// segments (`.pnpm/react@19.2.4/…`) from being read as a method.
|
|
61
|
+
line.match(/^([^@/]*)@(.+):(\d+):(\d+)$/);
|
|
53
62
|
const stackFrameMatch = framedMatch ?? line.match(/^at\s+(?:.*\()?(.*):(\d+):(\d+)\)?$/) ?? line.match(/^(.*):(\d+):(\d+)$/);
|
|
54
63
|
if (!stackFrameMatch) {
|
|
55
64
|
continue;
|
|
@@ -99,30 +108,6 @@ function getFiberFromElement(element) {
|
|
|
99
108
|
if (fiberKey) {
|
|
100
109
|
return element[fiberKey] ?? null;
|
|
101
110
|
}
|
|
102
|
-
const instanceKey = keys.find((key) => {
|
|
103
|
-
return key.startsWith("__reactInternalInstance$");
|
|
104
|
-
});
|
|
105
|
-
if (instanceKey) {
|
|
106
|
-
return (
|
|
107
|
-
// Legacy cast - may or may not be necessary
|
|
108
|
-
// eslint-disable-next-line replo/no-as-cast
|
|
109
|
-
element[instanceKey] ?? null
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
const possibleFiberKey = keys.find((key) => {
|
|
113
|
-
if (!key.startsWith("__react")) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
const value = element[key];
|
|
117
|
-
return value && typeof value === "object" && "_debugSource" in value;
|
|
118
|
-
});
|
|
119
|
-
if (possibleFiberKey) {
|
|
120
|
-
return (
|
|
121
|
-
// Legacy cast - may or may not be necessary
|
|
122
|
-
// eslint-disable-next-line replo/no-as-cast
|
|
123
|
-
element[possibleFiberKey] ?? null
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
111
|
return null;
|
|
127
112
|
}
|
|
128
113
|
function getComponentName(fiber) {
|
|
@@ -140,28 +125,7 @@ function getComponentName(fiber) {
|
|
|
140
125
|
}
|
|
141
126
|
return void 0;
|
|
142
127
|
}
|
|
143
|
-
function findDebugSource(fiber
|
|
144
|
-
let current = fiber;
|
|
145
|
-
let depth = 0;
|
|
146
|
-
while (current && depth < maxDepth) {
|
|
147
|
-
if (current._debugSource) {
|
|
148
|
-
return {
|
|
149
|
-
source: current._debugSource,
|
|
150
|
-
componentName: getComponentName(current)
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
if (current._debugOwner?._debugSource) {
|
|
154
|
-
return {
|
|
155
|
-
source: current._debugOwner._debugSource,
|
|
156
|
-
componentName: getComponentName(current._debugOwner)
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
current = current.return;
|
|
160
|
-
depth++;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
function findDebugSourceReact19(fiber) {
|
|
128
|
+
function findDebugSource(fiber) {
|
|
165
129
|
let current = fiber;
|
|
166
130
|
let depth = 0;
|
|
167
131
|
const maxDepth = 50;
|
|
@@ -183,7 +147,7 @@ function getSourceLocation(element) {
|
|
|
183
147
|
while (currentElement) {
|
|
184
148
|
const fiber = getFiberFromElement(currentElement);
|
|
185
149
|
if (fiber) {
|
|
186
|
-
const debugInfo = findDebugSource(fiber)
|
|
150
|
+
const debugInfo = findDebugSource(fiber);
|
|
187
151
|
if (debugInfo?.source) {
|
|
188
152
|
return {
|
|
189
153
|
filePath: debugInfo.source.fileName,
|
|
@@ -198,27 +162,228 @@ function getSourceLocation(element) {
|
|
|
198
162
|
return void 0;
|
|
199
163
|
}
|
|
200
164
|
function getDebugSource(fiber) {
|
|
201
|
-
|
|
202
|
-
return debugInfo?.source;
|
|
165
|
+
return findDebugSource(fiber)?.source;
|
|
203
166
|
}
|
|
204
167
|
function getTagName(fiber) {
|
|
205
168
|
return z.string().safeParse(fiber.type).data?.toLowerCase();
|
|
206
169
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
170
|
+
const fiberSourceKeyCache = /* @__PURE__ */ new WeakMap();
|
|
171
|
+
function getFiberSourceKey(fiber) {
|
|
172
|
+
const cached = fiberSourceKeyCache.get(fiber);
|
|
173
|
+
if (cached !== void 0) {
|
|
174
|
+
return cached;
|
|
211
175
|
}
|
|
212
176
|
const source = getDebugSource(fiber);
|
|
213
|
-
|
|
177
|
+
const key = source ? `${source.fileName}:${source.lineNumber}:${source.columnNumber ?? ""}` : null;
|
|
178
|
+
fiberSourceKeyCache.set(fiber, key);
|
|
179
|
+
return key;
|
|
180
|
+
}
|
|
181
|
+
function findRepetitionGroup(fiber) {
|
|
182
|
+
const parent = fiber.return;
|
|
183
|
+
if (!parent) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const type = fiber.type;
|
|
187
|
+
const candidates = [];
|
|
188
|
+
let child = parent.child;
|
|
189
|
+
let scanned = 0;
|
|
190
|
+
while (child && scanned < MAX_SIBLING_SCAN) {
|
|
191
|
+
if (child.type === type) {
|
|
192
|
+
candidates.push(child);
|
|
193
|
+
}
|
|
194
|
+
child = child.sibling;
|
|
195
|
+
scanned += 1;
|
|
196
|
+
}
|
|
197
|
+
if (candidates.length < 2) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const sourceKey = getFiberSourceKey(fiber);
|
|
201
|
+
if (!sourceKey) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const members = candidates.filter((candidate) => {
|
|
205
|
+
return getFiberSourceKey(candidate) === sourceKey;
|
|
206
|
+
});
|
|
207
|
+
return members.length >= 2 ? members : null;
|
|
208
|
+
}
|
|
209
|
+
function descendChildPath(fiber, path) {
|
|
210
|
+
let current = fiber;
|
|
211
|
+
for (const childIndex of path) {
|
|
212
|
+
if (!current) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
let child = current.child;
|
|
216
|
+
let index = 0;
|
|
217
|
+
while (child && index < childIndex) {
|
|
218
|
+
child = child.sibling;
|
|
219
|
+
index += 1;
|
|
220
|
+
}
|
|
221
|
+
if (!child) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
current = child;
|
|
225
|
+
}
|
|
226
|
+
return current ?? null;
|
|
227
|
+
}
|
|
228
|
+
function getHostNode(fiber) {
|
|
229
|
+
return fiber?.stateNode instanceof HTMLElement ? fiber.stateNode : null;
|
|
230
|
+
}
|
|
231
|
+
function isMatchingHost(fiber, type, sourceKey) {
|
|
232
|
+
if (!fiber || !(fiber.stateNode instanceof HTMLElement)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return fiber.type === type && getFiberSourceKey(fiber) === sourceKey;
|
|
236
|
+
}
|
|
237
|
+
function findHostInSubtree(root, type, sourceKey) {
|
|
238
|
+
const stack = [];
|
|
239
|
+
let child = root.child;
|
|
240
|
+
while (child) {
|
|
241
|
+
stack.push(child);
|
|
242
|
+
child = child.sibling;
|
|
243
|
+
}
|
|
244
|
+
let scanned = 0;
|
|
245
|
+
while (stack.length > 0 && scanned < MAX_SUBTREE_SCAN) {
|
|
246
|
+
const current = stack.pop();
|
|
247
|
+
scanned += 1;
|
|
248
|
+
if (!current) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (isMatchingHost(current, type, sourceKey)) {
|
|
252
|
+
return getHostNode(current);
|
|
253
|
+
}
|
|
254
|
+
let next = current.child;
|
|
255
|
+
while (next) {
|
|
256
|
+
stack.push(next);
|
|
257
|
+
next = next.sibling;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function resolveCurrentFiberByParentLinkage(fiber) {
|
|
263
|
+
const alternate = fiber.alternate;
|
|
264
|
+
if (!alternate) {
|
|
265
|
+
return fiber;
|
|
266
|
+
}
|
|
267
|
+
let child = fiber.return?.child;
|
|
268
|
+
let scanned = 0;
|
|
269
|
+
while (child && scanned < MAX_SIBLING_SCAN) {
|
|
270
|
+
if (child === fiber) {
|
|
271
|
+
return fiber;
|
|
272
|
+
}
|
|
273
|
+
child = child.sibling;
|
|
274
|
+
scanned += 1;
|
|
275
|
+
}
|
|
276
|
+
return alternate;
|
|
277
|
+
}
|
|
278
|
+
function getFiberRootCurrent(stateNode) {
|
|
279
|
+
if (!stateNode || typeof stateNode !== "object" || !("current" in stateNode)) {
|
|
214
280
|
return null;
|
|
215
281
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
282
|
+
const current = Reflect.get(stateNode, "current");
|
|
283
|
+
if (current && typeof current === "object" && "child" in current) {
|
|
284
|
+
return current;
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
function resolveCurrentFiber(fiber) {
|
|
289
|
+
const alternate = fiber.alternate;
|
|
290
|
+
if (!alternate) {
|
|
291
|
+
return fiber;
|
|
292
|
+
}
|
|
293
|
+
let rootFiber = fiber;
|
|
294
|
+
let climbed = 0;
|
|
295
|
+
while (rootFiber.return && climbed < MAX_ROOT_CLIMB_DEPTH) {
|
|
296
|
+
rootFiber = rootFiber.return;
|
|
297
|
+
climbed += 1;
|
|
298
|
+
}
|
|
299
|
+
const currentRoot = rootFiber.return ? null : getFiberRootCurrent(rootFiber.stateNode);
|
|
300
|
+
if (!currentRoot) {
|
|
301
|
+
return resolveCurrentFiberByParentLinkage(fiber);
|
|
302
|
+
}
|
|
303
|
+
const stack = [currentRoot];
|
|
304
|
+
let scanned = 0;
|
|
305
|
+
while (stack.length > 0 && scanned < MAX_CURRENT_TREE_SCAN) {
|
|
306
|
+
const node = stack.pop();
|
|
307
|
+
scanned += 1;
|
|
308
|
+
if (!node) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (node === fiber || node === alternate) {
|
|
312
|
+
return node;
|
|
313
|
+
}
|
|
314
|
+
if (node.child) {
|
|
315
|
+
stack.push(node.child);
|
|
316
|
+
}
|
|
317
|
+
if (node.sibling) {
|
|
318
|
+
stack.push(node.sibling);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (scanned >= MAX_CURRENT_TREE_SCAN) {
|
|
322
|
+
return resolveCurrentFiberByParentLinkage(fiber);
|
|
323
|
+
}
|
|
324
|
+
return fiber;
|
|
325
|
+
}
|
|
326
|
+
function getRepetitionHostNodes(element) {
|
|
327
|
+
const cachedFiber = getFiberFromElement(element);
|
|
328
|
+
if (!cachedFiber) {
|
|
329
|
+
return [element];
|
|
330
|
+
}
|
|
331
|
+
const selectedFiber = resolveCurrentFiber(cachedFiber);
|
|
332
|
+
const climb = [];
|
|
333
|
+
let cursor = selectedFiber;
|
|
334
|
+
let depth = 0;
|
|
335
|
+
let unit = null;
|
|
336
|
+
let group = null;
|
|
337
|
+
while (cursor && depth < MAX_FIBER_CHAIN_DEPTH) {
|
|
338
|
+
climb.push(cursor);
|
|
339
|
+
const candidate = findRepetitionGroup(cursor);
|
|
340
|
+
if (candidate) {
|
|
341
|
+
unit = cursor;
|
|
342
|
+
group = candidate;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
cursor = cursor.return;
|
|
346
|
+
depth += 1;
|
|
347
|
+
}
|
|
348
|
+
if (!unit || !group) {
|
|
349
|
+
return [element];
|
|
350
|
+
}
|
|
351
|
+
const downward = climb.slice(0, climb.indexOf(unit) + 1).reverse();
|
|
352
|
+
let pathFromUnit = [];
|
|
353
|
+
for (let i = 0; i < downward.length - 1 && pathFromUnit; i += 1) {
|
|
354
|
+
const parentFiber = downward[i];
|
|
355
|
+
const targetFiber = downward[i + 1];
|
|
356
|
+
if (!parentFiber || !targetFiber) {
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
let child = parentFiber.child;
|
|
360
|
+
let index = 0;
|
|
361
|
+
while (child && child !== targetFiber && child !== targetFiber.alternate) {
|
|
362
|
+
child = child.sibling;
|
|
363
|
+
index += 1;
|
|
364
|
+
}
|
|
365
|
+
if (child) {
|
|
366
|
+
pathFromUnit.push(index);
|
|
367
|
+
} else {
|
|
368
|
+
pathFromUnit = null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const selectedType = selectedFiber.type;
|
|
372
|
+
const selectedSourceKey = getFiberSourceKey(selectedFiber);
|
|
373
|
+
const nodes = [];
|
|
374
|
+
const seen = /* @__PURE__ */ new Set();
|
|
375
|
+
for (const member of group) {
|
|
376
|
+
const landed = pathFromUnit ? descendChildPath(member, pathFromUnit) : null;
|
|
377
|
+
const node = isMatchingHost(landed, selectedType, selectedSourceKey) ? getHostNode(landed) : findHostInSubtree(member, selectedType, selectedSourceKey);
|
|
378
|
+
if (node && !seen.has(node)) {
|
|
379
|
+
seen.add(node);
|
|
380
|
+
nodes.push(node);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!seen.has(element)) {
|
|
384
|
+
nodes.push(element);
|
|
385
|
+
}
|
|
386
|
+
return nodes;
|
|
222
387
|
}
|
|
223
388
|
function getSerializablePropValue(value) {
|
|
224
389
|
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
@@ -296,11 +461,10 @@ function getFiberChain(element) {
|
|
|
296
461
|
}
|
|
297
462
|
export {
|
|
298
463
|
findDebugSource,
|
|
299
|
-
findDebugSourceReact19,
|
|
300
464
|
getComponentName,
|
|
301
465
|
getFiberChain,
|
|
302
466
|
getFiberFromElement,
|
|
303
|
-
|
|
467
|
+
getRepetitionHostNodes,
|
|
304
468
|
getSourceLocation
|
|
305
469
|
};
|
|
306
470
|
//# sourceMappingURL=fiber.js.map
|