@replohq/sandbox-runtime 1.7.10 → 1.7.11
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/replo-utils/react/zustand.d.ts +13 -0
- package/_vendor/replo-utils/react/zustand.mjs +24 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +12 -2
- package/_vendor/schemas/selectModeIframeRpc.d.ts +66 -4
- package/_vendor/schemas/selectModeIframeRpc.mjs +16 -3
- package/buildMetadata.js +3 -3
- package/package.json +6 -5
- package/select-mode/EditorSelection.d.ts +1 -22
- package/select-mode/EditorSelection.js +61 -100
- package/select-mode/SelectionFrames.d.ts +1 -0
- package/select-mode/SelectionFrames.js +58 -0
- package/select-mode/annotationFrames.d.ts +13 -0
- package/select-mode/annotationFrames.js +31 -1
- package/select-mode/displayModeStore.d.ts +0 -2
- package/select-mode/displayModeStore.js +11 -30
- package/select-mode/hooks/useParentMessaging.js +3 -0
- package/select-mode/hooks/useSelectionHover.d.ts +1 -19
- package/select-mode/hooks/useSelectionHover.js +37 -49
- package/select-mode/hooks/useSelectionInteractions.d.ts +1 -12
- package/select-mode/hooks/useSelectionInteractions.js +57 -39
- package/select-mode/hooks/useSelectionScrollSync.d.ts +1 -25
- package/select-mode/hooks/useSelectionScrollSync.js +48 -40
- package/select-mode/iframeRpcChannel.d.ts +24 -1
- package/select-mode/inlineEditFeedback.d.ts +6 -0
- package/select-mode/inlineEditFeedback.js +47 -0
- package/select-mode/isTextEditableElement.d.ts +1 -0
- package/select-mode/isTextEditableElement.js +15 -0
- package/select-mode/selectModeStore.d.ts +160 -0
- package/select-mode/selectModeStore.js +585 -0
- package/select-mode/selectionFrame.d.ts +10 -59
- package/select-mode/selectionFrame.js +26 -150
- package/select-mode/symbolicate.js +4 -0
- package/select-mode/tiptapInlineEditor.js +16 -1
- package/select-mode/hooks/useCrosshairCursor.d.ts +0 -10
- package/select-mode/hooks/useCrosshairCursor.js +0 -23
- package/select-mode/hooks/useEditModeStateMachine.d.ts +0 -16
- package/select-mode/hooks/useEditModeStateMachine.js +0 -228
- package/select-mode/hooks/useSelectModeController.d.ts +0 -11
- package/select-mode/hooks/useSelectModeController.js +0 -72
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StateCreator, StoreApi } from "zustand";
|
|
2
|
+
/**
|
|
3
|
+
* Drop-in replacement for zustand's `useStore` that uses shallow equality by default.
|
|
4
|
+
* Use this in context-based stores so all selectors automatically get shallow comparison.
|
|
5
|
+
* Subscribes through React's useSyncExternalStore so the consumer's React is
|
|
6
|
+
* used, not Zustand's nested use-sync-external-store copy.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useShallowStore<StateType, SelectedType>(store: StoreApi<StateType>, selector: (state: StateType) => SelectedType): SelectedType;
|
|
9
|
+
/**
|
|
10
|
+
* Drop-in replacement for zustand's `create` that uses shallow equality by default.
|
|
11
|
+
* Use this instead of `create()` so all selectors automatically get shallow comparison.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createShallowStore<StateType>(initializer: StateCreator<StateType, [], []>): import("zustand/traditional").UseBoundStoreWithEqualityFn<StoreApi<StateType>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useRef, useSyncExternalStore } from 'react';
|
|
2
|
+
import { shallow } from 'zustand/shallow';
|
|
3
|
+
import { createWithEqualityFn } from 'zustand/traditional';
|
|
4
|
+
|
|
5
|
+
function useShallowStore(store, selector) {
|
|
6
|
+
const lastSelectedRef = useRef(void 0);
|
|
7
|
+
return useSyncExternalStore(
|
|
8
|
+
store.subscribe,
|
|
9
|
+
() => {
|
|
10
|
+
const next = selector(store.getState());
|
|
11
|
+
if (lastSelectedRef.current !== void 0 && shallow(lastSelectedRef.current, next)) {
|
|
12
|
+
return lastSelectedRef.current;
|
|
13
|
+
}
|
|
14
|
+
lastSelectedRef.current = next;
|
|
15
|
+
return next;
|
|
16
|
+
},
|
|
17
|
+
() => selector(store.getState())
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function createShallowStore(initializer) {
|
|
21
|
+
return createWithEqualityFn(initializer, shallow);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { createShallowStore, useShallowStore };
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { SelectedElement, DisplayMode, SelectModeSelectionType, SelectModeOptimisticUpdatePayload, SelectModeFormatCommand, SelectableData,
|
|
1
|
+
import type { SelectModeBoundingRect, SelectedElement, DisplayMode, SelectModeSelectionType, SelectModeOptimisticUpdatePayload, SelectModeFormatCommand, SelectableData, SelectModeClickPosition, SelectModeSelectionRect, SelectModeTiptapActiveMarks } from "./selectMode";
|
|
2
|
+
export type InlineEditFeedback = {
|
|
3
|
+
status: "pending" | "unavailable" | "failed";
|
|
4
|
+
rect: SelectModeBoundingRect;
|
|
5
|
+
};
|
|
2
6
|
export type SelectedComponentEditRequest = {
|
|
3
7
|
siteId: string;
|
|
4
8
|
element: SelectedElement;
|
|
@@ -105,11 +109,17 @@ export type SelectModeIframeMessage = {
|
|
|
105
109
|
};
|
|
106
110
|
originalInnerHTML: string;
|
|
107
111
|
};
|
|
112
|
+
} | {
|
|
113
|
+
method: "inlineEditFeedbackChanged";
|
|
114
|
+
data: {
|
|
115
|
+
feedback: InlineEditFeedback | null;
|
|
116
|
+
};
|
|
108
117
|
} | {
|
|
109
118
|
method: "editorStateUpdate";
|
|
110
119
|
data: {
|
|
111
120
|
activeMarks: SelectModeTiptapActiveMarks;
|
|
112
|
-
|
|
121
|
+
isInlineEditing: boolean;
|
|
122
|
+
isInlineEditPending: boolean;
|
|
113
123
|
};
|
|
114
124
|
} | {
|
|
115
125
|
method: "fastRefreshComplete";
|
|
@@ -2,6 +2,23 @@ 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 inlineEditFeedbackSchema: z.ZodObject<{
|
|
6
|
+
status: z.ZodEnum<{
|
|
7
|
+
unavailable: "unavailable";
|
|
8
|
+
pending: "pending";
|
|
9
|
+
failed: "failed";
|
|
10
|
+
}>;
|
|
11
|
+
rect: z.ZodObject<{
|
|
12
|
+
x: z.ZodNumber;
|
|
13
|
+
y: z.ZodNumber;
|
|
14
|
+
left: z.ZodNumber;
|
|
15
|
+
top: z.ZodNumber;
|
|
16
|
+
right: z.ZodNumber;
|
|
17
|
+
bottom: z.ZodNumber;
|
|
18
|
+
width: z.ZodNumber;
|
|
19
|
+
height: z.ZodNumber;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
}, z.core.$strip>;
|
|
5
22
|
export declare const iframeRuntimeBuildMetadataSchema: z.ZodObject<{
|
|
6
23
|
packageName: z.ZodString;
|
|
7
24
|
version: z.ZodString;
|
|
@@ -258,8 +275,8 @@ export declare const websiteBuilderToCanopyMethodConfig: {
|
|
|
258
275
|
inputSchema: z.ZodObject<{
|
|
259
276
|
sessionId: z.ZodString;
|
|
260
277
|
outcome: z.ZodEnum<{
|
|
261
|
-
persisted: "persisted";
|
|
262
278
|
failed: "failed";
|
|
279
|
+
persisted: "persisted";
|
|
263
280
|
}>;
|
|
264
281
|
}, z.core.$strip>;
|
|
265
282
|
outputSchema: z.ZodVoid;
|
|
@@ -621,6 +638,28 @@ export declare const canopyToWebsiteBuilderMethodConfig: {
|
|
|
621
638
|
}, z.core.$strip>;
|
|
622
639
|
outputSchema: z.ZodVoid;
|
|
623
640
|
};
|
|
641
|
+
inlineEditFeedbackChanged: {
|
|
642
|
+
inputSchema: z.ZodObject<{
|
|
643
|
+
feedback: z.ZodNullable<z.ZodObject<{
|
|
644
|
+
status: z.ZodEnum<{
|
|
645
|
+
unavailable: "unavailable";
|
|
646
|
+
pending: "pending";
|
|
647
|
+
failed: "failed";
|
|
648
|
+
}>;
|
|
649
|
+
rect: z.ZodObject<{
|
|
650
|
+
x: z.ZodNumber;
|
|
651
|
+
y: z.ZodNumber;
|
|
652
|
+
left: z.ZodNumber;
|
|
653
|
+
top: z.ZodNumber;
|
|
654
|
+
right: z.ZodNumber;
|
|
655
|
+
bottom: z.ZodNumber;
|
|
656
|
+
width: z.ZodNumber;
|
|
657
|
+
height: z.ZodNumber;
|
|
658
|
+
}, z.core.$strip>;
|
|
659
|
+
}, z.core.$strip>>;
|
|
660
|
+
}, z.core.$strip>;
|
|
661
|
+
outputSchema: z.ZodVoid;
|
|
662
|
+
};
|
|
624
663
|
editorStateUpdate: {
|
|
625
664
|
inputSchema: z.ZodObject<{
|
|
626
665
|
activeMarks: z.ZodObject<{
|
|
@@ -629,7 +668,8 @@ export declare const canopyToWebsiteBuilderMethodConfig: {
|
|
|
629
668
|
underline: z.ZodBoolean;
|
|
630
669
|
strike: z.ZodBoolean;
|
|
631
670
|
}, z.core.$strip>;
|
|
632
|
-
|
|
671
|
+
isInlineEditing: z.ZodBoolean;
|
|
672
|
+
isInlineEditPending: z.ZodBoolean;
|
|
633
673
|
}, z.core.$strip>;
|
|
634
674
|
outputSchema: z.ZodVoid;
|
|
635
675
|
};
|
|
@@ -758,8 +798,8 @@ export declare const selectModeParentMessageSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
758
798
|
data: z.ZodObject<{
|
|
759
799
|
sessionId: z.ZodString;
|
|
760
800
|
outcome: z.ZodEnum<{
|
|
761
|
-
persisted: "persisted";
|
|
762
801
|
failed: "failed";
|
|
802
|
+
persisted: "persisted";
|
|
763
803
|
}>;
|
|
764
804
|
}, z.core.$strip>;
|
|
765
805
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -1036,6 +1076,27 @@ export declare const selectModeIframeMessageSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
1036
1076
|
json: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
1037
1077
|
originalInnerHTML: z.ZodString;
|
|
1038
1078
|
}, z.core.$strip>;
|
|
1079
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1080
|
+
method: z.ZodLiteral<"inlineEditFeedbackChanged">;
|
|
1081
|
+
data: z.ZodObject<{
|
|
1082
|
+
feedback: z.ZodNullable<z.ZodObject<{
|
|
1083
|
+
status: z.ZodEnum<{
|
|
1084
|
+
unavailable: "unavailable";
|
|
1085
|
+
pending: "pending";
|
|
1086
|
+
failed: "failed";
|
|
1087
|
+
}>;
|
|
1088
|
+
rect: z.ZodObject<{
|
|
1089
|
+
x: z.ZodNumber;
|
|
1090
|
+
y: z.ZodNumber;
|
|
1091
|
+
left: z.ZodNumber;
|
|
1092
|
+
top: z.ZodNumber;
|
|
1093
|
+
right: z.ZodNumber;
|
|
1094
|
+
bottom: z.ZodNumber;
|
|
1095
|
+
width: z.ZodNumber;
|
|
1096
|
+
height: z.ZodNumber;
|
|
1097
|
+
}, z.core.$strip>;
|
|
1098
|
+
}, z.core.$strip>>;
|
|
1099
|
+
}, z.core.$strip>;
|
|
1039
1100
|
}, z.core.$strip>, z.ZodObject<{
|
|
1040
1101
|
method: z.ZodLiteral<"editorStateUpdate">;
|
|
1041
1102
|
data: z.ZodObject<{
|
|
@@ -1045,7 +1106,8 @@ export declare const selectModeIframeMessageSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
1045
1106
|
underline: z.ZodBoolean;
|
|
1046
1107
|
strike: z.ZodBoolean;
|
|
1047
1108
|
}, z.core.$strip>;
|
|
1048
|
-
|
|
1109
|
+
isInlineEditing: z.ZodBoolean;
|
|
1110
|
+
isInlineEditPending: z.ZodBoolean;
|
|
1049
1111
|
}, z.core.$strip>;
|
|
1050
1112
|
}, z.core.$strip>, z.ZodObject<{
|
|
1051
1113
|
method: z.ZodLiteral<"fastRefreshComplete">;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { selectedElementSchema, selectModeFormatCommandSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionTypeSchema, displayModeSchema, selectModeTiptapActiveMarksSchema,
|
|
1
|
+
import { selectModeBoundingRectSchema, selectedElementSchema, selectModeFormatCommandSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionTypeSchema, displayModeSchema, selectModeTiptapActiveMarksSchema, selectableDataSchema, selectModeSelectionRectSchema, selectModeClickPositionSchema } from '../../chunk-4T7WSMC3.mjs';
|
|
2
2
|
import '../../chunk-JUKCUPQ6.mjs';
|
|
3
3
|
import '../../chunk-FLYEIRFK.mjs';
|
|
4
4
|
import '../../chunk-TESFJQID.mjs';
|
|
@@ -7,6 +7,10 @@ import { z } from 'zod';
|
|
|
7
7
|
|
|
8
8
|
var SELECT_MODE_IFRAME_RPC_CHANNEL_ID = "replo:select-mode-iframe-rpc";
|
|
9
9
|
var SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION = 1;
|
|
10
|
+
var inlineEditFeedbackSchema = z.object({
|
|
11
|
+
status: z.enum(["pending", "unavailable", "failed"]),
|
|
12
|
+
rect: selectModeBoundingRectSchema
|
|
13
|
+
}).describe("InlineEditFeedback");
|
|
10
14
|
var iframeRuntimeBuildMetadataSchema = z.object({
|
|
11
15
|
packageName: z.string(),
|
|
12
16
|
version: z.string(),
|
|
@@ -144,10 +148,15 @@ var canopyToWebsiteBuilderMethodConfig = {
|
|
|
144
148
|
}),
|
|
145
149
|
outputSchema: z.void()
|
|
146
150
|
},
|
|
151
|
+
inlineEditFeedbackChanged: {
|
|
152
|
+
inputSchema: z.object({ feedback: inlineEditFeedbackSchema.nullable() }),
|
|
153
|
+
outputSchema: z.void()
|
|
154
|
+
},
|
|
147
155
|
editorStateUpdate: {
|
|
148
156
|
inputSchema: z.object({
|
|
149
157
|
activeMarks: selectModeTiptapActiveMarksSchema,
|
|
150
|
-
|
|
158
|
+
isInlineEditing: z.boolean(),
|
|
159
|
+
isInlineEditPending: z.boolean()
|
|
151
160
|
}),
|
|
152
161
|
outputSchema: z.void()
|
|
153
162
|
},
|
|
@@ -229,6 +238,10 @@ var selectModeIframeMessageSchema = z.discriminatedUnion("method", [
|
|
|
229
238
|
method: z.literal("textCommitted"),
|
|
230
239
|
data: canopyToWebsiteBuilderMethodConfig.textCommitted.inputSchema
|
|
231
240
|
}),
|
|
241
|
+
z.object({
|
|
242
|
+
method: z.literal("inlineEditFeedbackChanged"),
|
|
243
|
+
data: canopyToWebsiteBuilderMethodConfig.inlineEditFeedbackChanged.inputSchema
|
|
244
|
+
}),
|
|
232
245
|
z.object({
|
|
233
246
|
method: z.literal("editorStateUpdate"),
|
|
234
247
|
data: canopyToWebsiteBuilderMethodConfig.editorStateUpdate.inputSchema
|
|
@@ -238,4 +251,4 @@ var selectModeIframeMessageSchema = z.discriminatedUnion("method", [
|
|
|
238
251
|
})
|
|
239
252
|
]).describe("SelectModeIframeMessage");
|
|
240
253
|
|
|
241
|
-
export { SELECT_MODE_IFRAME_RPC_CHANNEL_ID, SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION, canopyToWebsiteBuilderMethodConfig, iframeRuntimeBuildMetadataSchema, selectModeIframeMessageSchema, selectModeParentMessageSchema, selectedComponentEditRequestSchema, websiteBuilderToCanopyMethodConfig };
|
|
254
|
+
export { SELECT_MODE_IFRAME_RPC_CHANNEL_ID, SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION, canopyToWebsiteBuilderMethodConfig, iframeRuntimeBuildMetadataSchema, inlineEditFeedbackSchema, selectModeIframeMessageSchema, selectModeParentMessageSchema, selectedComponentEditRequestSchema, websiteBuilderToCanopyMethodConfig };
|
package/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-
|
|
5
|
+
"commit": "14ba23d4044e71ee124025f7b496f0cc9f6e4b06",
|
|
6
|
+
"builtAt": "2026-09-21T17:30:09.582Z"
|
|
7
7
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sandbox-runtime",
|
|
6
|
-
"version": "1.7.
|
|
6
|
+
"version": "1.7.11",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-
|
|
8
|
+
"commit": "14ba23d4044e71ee124025f7b496f0cc9f6e4b06",
|
|
9
|
+
"builtAt": "2026-09-21T17:30:09.582Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"@tiptap/extension-underline": "^2.25.0",
|
|
42
42
|
"@tiptap/pm": "^2.25.0",
|
|
43
43
|
"uuid": "^11.1.0",
|
|
44
|
-
"zod": "^4.3.6"
|
|
44
|
+
"zod": "^4.3.6",
|
|
45
|
+
"zustand": "^4.5.7"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
48
|
"@types/react": ">=19.0.0",
|
|
@@ -1,22 +1 @@
|
|
|
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
|
-
export declare function EditorSelection(): null;
|
|
1
|
+
export declare function EditorSelection(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,39 +1,29 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
buildElementDescriptor,
|
|
5
|
-
buildSelector
|
|
6
|
-
} from "./descriptor/elementDescriptor";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
7
4
|
import { installFastRefreshRelay } from "./fast-refresh-relay";
|
|
8
|
-
import { useCrosshairCursor } from "./hooks/useCrosshairCursor";
|
|
9
|
-
import { useEditModeStateMachine } from "./hooks/useEditModeStateMachine";
|
|
10
5
|
import { useOptimisticOverrides } from "./hooks/useOptimisticOverrides";
|
|
11
6
|
import { useParentMessaging } from "./hooks/useParentMessaging";
|
|
12
7
|
import { useSelectionHover } from "./hooks/useSelectionHover";
|
|
13
8
|
import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
|
|
14
9
|
import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
15
|
-
import { useSelectModeController } from "./hooks/useSelectModeController";
|
|
16
|
-
import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
|
|
17
|
-
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
18
|
-
import { getDocumentScrollOffset } from "./interactions/selectionGeometry";
|
|
19
10
|
import { PREVIEW_RENDERED_EVENT } from "./previewRendered";
|
|
20
|
-
import {
|
|
21
|
-
import { getConnectedSelectedElement } from "./selectModeSession";
|
|
11
|
+
import { SelectionFrames } from "./SelectionFrames";
|
|
22
12
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
useDisplayMode,
|
|
14
|
+
useSelectModeStore,
|
|
15
|
+
useSelectModeStoreActions
|
|
16
|
+
} from "./selectModeStore";
|
|
26
17
|
function EditorSelection() {
|
|
18
|
+
const store = useSelectModeStore();
|
|
19
|
+
const displayMode = useDisplayMode();
|
|
27
20
|
const {
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
formatCommand,
|
|
22
|
+
postSelectedElementSnapshot,
|
|
23
|
+
revertInlineEdit,
|
|
30
24
|
setDisplayMode,
|
|
31
25
|
setSelectionType
|
|
32
|
-
} =
|
|
33
|
-
const { beginEditing, editingElementRef, formatCommand, revert } = useEditModeStateMachine({
|
|
34
|
-
displayMode,
|
|
35
|
-
interactionSessionRef
|
|
36
|
-
});
|
|
26
|
+
} = useSelectModeStoreActions();
|
|
37
27
|
const {
|
|
38
28
|
applyOptimisticUpdate,
|
|
39
29
|
markCommitted,
|
|
@@ -41,56 +31,6 @@ function EditorSelection() {
|
|
|
41
31
|
resolveSessionOverrides,
|
|
42
32
|
revertOverride
|
|
43
33
|
} = useOptimisticOverrides();
|
|
44
|
-
const postSelectedElementSnapshot = useCallback(
|
|
45
|
-
({ clearMissing = false } = {}) => {
|
|
46
|
-
const session = interactionSessionRef.current;
|
|
47
|
-
if (session.type !== "element") {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
const element = getConnectedSelectedElement(session);
|
|
51
|
-
if (!element) {
|
|
52
|
-
if (!clearMissing) return false;
|
|
53
|
-
setSelectionType({ selectionType: "none" });
|
|
54
|
-
getWebsiteBuilderRpc()?.areaSelected({
|
|
55
|
-
rect: session.data.rect,
|
|
56
|
-
items: []
|
|
57
|
-
});
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
const siblingElements = getRepetitionSiblingElements({ element });
|
|
61
|
-
const descriptor = buildElementDescriptor({
|
|
62
|
-
element,
|
|
63
|
-
clickPosition: session.clickPosition ?? (session.data.kind === "react-element" ? session.data.clickPosition : { x: 0, y: 0 }),
|
|
64
|
-
siblingSelectors: siblingElements.map(buildSelector)
|
|
65
|
-
});
|
|
66
|
-
if (descriptor.kind !== "react-element") {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
const refreshedSession = {
|
|
70
|
-
...session,
|
|
71
|
-
element,
|
|
72
|
-
siblingElements,
|
|
73
|
-
data: session.data
|
|
74
|
-
};
|
|
75
|
-
interactionSessionRef.current = refreshedSession;
|
|
76
|
-
showElementSelectionFrames({
|
|
77
|
-
elements: [element, ...siblingElements],
|
|
78
|
-
scrollOffset: getDocumentScrollOffset()
|
|
79
|
-
});
|
|
80
|
-
void symbolicateSelectedElement(descriptor).then((symbolicated) => {
|
|
81
|
-
if (interactionSessionRef.current !== refreshedSession) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (symbolicated.sourceLocation && !isUserSourceLocation(symbolicated.sourceLocation)) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
refreshedSession.data = symbolicated;
|
|
88
|
-
getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });
|
|
89
|
-
});
|
|
90
|
-
return true;
|
|
91
|
-
},
|
|
92
|
-
[interactionSessionRef, setSelectionType]
|
|
93
|
-
);
|
|
94
34
|
useEffect(() => {
|
|
95
35
|
let refreshTimeout;
|
|
96
36
|
let missingTimeout;
|
|
@@ -98,15 +38,15 @@ function EditorSelection() {
|
|
|
98
38
|
const scheduleSnapshot = () => {
|
|
99
39
|
clearTimeout(refreshTimeout);
|
|
100
40
|
refreshTimeout = setTimeout(() => {
|
|
101
|
-
if (
|
|
41
|
+
if (store.getState().editingElement) return;
|
|
102
42
|
if (postSelectedElementSnapshot()) {
|
|
103
43
|
clearTimeout(missingTimeout);
|
|
104
44
|
missingTimeout = void 0;
|
|
105
45
|
} else if (!missingTimeout) {
|
|
106
|
-
const missingSession =
|
|
46
|
+
const missingSession = store.getState().interactionSession;
|
|
107
47
|
missingTimeout = setTimeout(() => {
|
|
108
48
|
missingTimeout = void 0;
|
|
109
|
-
if (
|
|
49
|
+
if (store.getState().interactionSession === missingSession) {
|
|
110
50
|
postSelectedElementSnapshot({ clearMissing: true });
|
|
111
51
|
}
|
|
112
52
|
}, 5e3);
|
|
@@ -114,14 +54,14 @@ function EditorSelection() {
|
|
|
114
54
|
}, 250);
|
|
115
55
|
};
|
|
116
56
|
const observer = new MutationObserver((records) => {
|
|
117
|
-
const session =
|
|
118
|
-
if (
|
|
57
|
+
const session = store.getState().interactionSession;
|
|
58
|
+
if (store.getState().editingElement || session.type !== "element") return;
|
|
119
59
|
if (!session.element.isConnected || records.some((record) => session.element.contains(record.target))) {
|
|
120
60
|
scheduleSnapshot();
|
|
121
61
|
}
|
|
122
62
|
});
|
|
123
63
|
const watchRender = () => {
|
|
124
|
-
if (
|
|
64
|
+
if (store.getState().editingElement || store.getState().interactionSession.type !== "element")
|
|
125
65
|
return;
|
|
126
66
|
clearTimeout(observationTimeout);
|
|
127
67
|
observer.observe(document.body, {
|
|
@@ -133,7 +73,7 @@ function EditorSelection() {
|
|
|
133
73
|
};
|
|
134
74
|
installFastRefreshRelay({
|
|
135
75
|
onComplete: () => {
|
|
136
|
-
reapplyOverrides({ skipElement:
|
|
76
|
+
reapplyOverrides({ skipElement: store.getState().editingElement });
|
|
137
77
|
watchRender();
|
|
138
78
|
}
|
|
139
79
|
});
|
|
@@ -146,35 +86,56 @@ function EditorSelection() {
|
|
|
146
86
|
window.removeEventListener(PREVIEW_RENDERED_EVENT, watchRender);
|
|
147
87
|
installFastRefreshRelay();
|
|
148
88
|
};
|
|
149
|
-
}, [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
89
|
+
}, [store, postSelectedElementSnapshot, reapplyOverrides]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const cancelOnPointerDown = (event) => {
|
|
92
|
+
const { editingElement, inlineEditRequest, actions } = store.getState();
|
|
93
|
+
if (event.button === 0 && editingElement && event.target instanceof Node && !editingElement.contains(event.target)) {
|
|
94
|
+
actions.commitInlineEdit();
|
|
95
|
+
}
|
|
96
|
+
if (inlineEditRequest && event.target instanceof Node && !inlineEditRequest.element.contains(event.target)) {
|
|
97
|
+
actions.dismissInlineEditRequest();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const cancelOnEscape = (event) => {
|
|
101
|
+
const { inlineEditRequest, actions } = store.getState();
|
|
102
|
+
if (!inlineEditRequest || event.key !== "Escape") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (inlineEditRequest.status === "pending") {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
}
|
|
108
|
+
actions.dismissInlineEditRequest();
|
|
109
|
+
};
|
|
110
|
+
document.addEventListener("pointerdown", cancelOnPointerDown, true);
|
|
111
|
+
document.addEventListener("keydown", cancelOnEscape, true);
|
|
112
|
+
return () => {
|
|
113
|
+
document.removeEventListener("pointerdown", cancelOnPointerDown, true);
|
|
114
|
+
document.removeEventListener("keydown", cancelOnEscape, true);
|
|
115
|
+
store.getState().actions.revertInlineEdit();
|
|
116
|
+
};
|
|
117
|
+
}, [store]);
|
|
155
118
|
useParentMessaging({
|
|
156
119
|
applyOptimisticUpdate,
|
|
157
120
|
markCommitted,
|
|
158
|
-
onExitEditMode:
|
|
121
|
+
onExitEditMode: revertInlineEdit,
|
|
159
122
|
onFormatCommand: formatCommand,
|
|
160
123
|
resolveSessionOverrides,
|
|
161
124
|
revertOverride,
|
|
162
125
|
setDisplayMode,
|
|
163
126
|
setSelectionType
|
|
164
127
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
177
|
-
return null;
|
|
128
|
+
useSelectionInteractions();
|
|
129
|
+
useSelectionHover();
|
|
130
|
+
useSelectionScrollSync();
|
|
131
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
132
|
+
displayMode === "selection" && /* @__PURE__ */ jsx("style", { children: `
|
|
133
|
+
* { cursor: crosshair !important; user-select: none !important; -webkit-user-drag: none !important; }
|
|
134
|
+
[data-select-mode-inline-editor][contenteditable="true"],
|
|
135
|
+
[data-select-mode-inline-editor][contenteditable="true"] * { cursor: text !important; }
|
|
136
|
+
` }),
|
|
137
|
+
/* @__PURE__ */ jsx(SelectionFrames, {})
|
|
138
|
+
] });
|
|
178
139
|
}
|
|
179
140
|
export {
|
|
180
141
|
EditorSelection
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SelectionFrames(): import("react").ReactPortal | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import {
|
|
5
|
+
SELECTION_FRAME_BORDER_HEX,
|
|
6
|
+
SELECTION_FRAME_EDITING_BORDER_HEX,
|
|
7
|
+
SELECTION_FRAMES_CONTAINER_ID
|
|
8
|
+
} from "./selectionFrame";
|
|
9
|
+
import {
|
|
10
|
+
useIsSelectionFrameEditing,
|
|
11
|
+
useSelectionFrames
|
|
12
|
+
} from "./selectModeStore";
|
|
13
|
+
const CONTAINER_STYLE = {
|
|
14
|
+
position: "absolute",
|
|
15
|
+
top: 0,
|
|
16
|
+
left: 0,
|
|
17
|
+
width: 0,
|
|
18
|
+
height: 0,
|
|
19
|
+
margin: 0,
|
|
20
|
+
padding: 0,
|
|
21
|
+
pointerEvents: "none",
|
|
22
|
+
zIndex: 2147483646
|
|
23
|
+
};
|
|
24
|
+
function SelectionFrames() {
|
|
25
|
+
const frames = useSelectionFrames();
|
|
26
|
+
const isEditing = useIsSelectionFrameEditing();
|
|
27
|
+
if (typeof document === "undefined" || !document.body || frames.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const borderColor = isEditing ? SELECTION_FRAME_EDITING_BORDER_HEX : SELECTION_FRAME_BORDER_HEX;
|
|
31
|
+
return createPortal(
|
|
32
|
+
/* @__PURE__ */ jsx("div", { id: SELECTION_FRAMES_CONTAINER_ID, "aria-hidden": true, style: CONTAINER_STYLE, children: frames.map((frame, index) => /* @__PURE__ */ jsx(
|
|
33
|
+
"div",
|
|
34
|
+
{
|
|
35
|
+
"aria-hidden": true,
|
|
36
|
+
style: {
|
|
37
|
+
position: "absolute",
|
|
38
|
+
boxSizing: "border-box",
|
|
39
|
+
borderRadius: 2,
|
|
40
|
+
pointerEvents: "none",
|
|
41
|
+
margin: 0,
|
|
42
|
+
padding: 0,
|
|
43
|
+
left: frame.pageRect.x,
|
|
44
|
+
top: frame.pageRect.y,
|
|
45
|
+
width: frame.pageRect.width,
|
|
46
|
+
height: frame.pageRect.height,
|
|
47
|
+
border: `2px solid ${borderColor}`,
|
|
48
|
+
background: frame.variant === "area" ? "rgba(192, 38, 211, 0.08)" : "transparent"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
`${frame.variant}-${index}`
|
|
52
|
+
)) }),
|
|
53
|
+
document.body
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
SelectionFrames
|
|
58
|
+
};
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-iframe annotation frame renderer — `selectionFrame`'s sibling surface.
|
|
3
|
+
*
|
|
4
|
+
* Same architecture and reasons as selection frames: page-coordinate
|
|
5
|
+
* `position: absolute` nodes in a body-level container, so document scroll
|
|
6
|
+
* translates them with zero JavaScript per scroll frame, and styling is
|
|
7
|
+
* vanilla inline CSS because customer pages have no tailwind.
|
|
8
|
+
*
|
|
9
|
+
* An annotation frame is an invisible positioning box over the annotated
|
|
10
|
+
* element carrying a numbered badge dot — the dot is the entire visual.
|
|
11
|
+
* Unlike selection frames these rebuild wholesale on each call: re-renders
|
|
12
|
+
* happen on set/re-measure (rare), never per scroll, so pooling buys nothing.
|
|
13
|
+
*/
|
|
1
14
|
export declare const ANNOTATION_CONTAINER_ID = "replo-annotation-frames";
|
|
2
15
|
interface AnnotationRect {
|
|
3
16
|
x: number;
|
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
import { getOrCreateFramesLayerContainer } from "./selectionFrame";
|
|
2
1
|
const ANNOTATION_CONTAINER_ID = "replo-annotation-frames";
|
|
2
|
+
function getOrCreateFramesLayerContainer({
|
|
3
|
+
id,
|
|
4
|
+
zIndex
|
|
5
|
+
}) {
|
|
6
|
+
if (typeof document === "undefined") {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const existing = document.getElementById(id);
|
|
10
|
+
if (existing instanceof HTMLDivElement) {
|
|
11
|
+
return existing;
|
|
12
|
+
}
|
|
13
|
+
if (!document.body) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const container = document.createElement("div");
|
|
17
|
+
container.id = id;
|
|
18
|
+
container.setAttribute("aria-hidden", "true");
|
|
19
|
+
container.style.cssText = [
|
|
20
|
+
"position: absolute",
|
|
21
|
+
"top: 0",
|
|
22
|
+
"left: 0",
|
|
23
|
+
"width: 0",
|
|
24
|
+
"height: 0",
|
|
25
|
+
"margin: 0",
|
|
26
|
+
"padding: 0",
|
|
27
|
+
"pointer-events: none",
|
|
28
|
+
`z-index: ${zIndex}`
|
|
29
|
+
].join(";");
|
|
30
|
+
document.body.append(container);
|
|
31
|
+
return container;
|
|
32
|
+
}
|
|
3
33
|
const ANNOTATION_FONT_STYLE_ID = "replo-annotation-font";
|
|
4
34
|
const BADGE_SIZE_PX = 30;
|
|
5
35
|
const EDITORIAL_ITALIC_FONT_URL = "https://assets.replocdn.com/PPEditorialNew-Italic.woff2";
|
|
@@ -4,7 +4,5 @@ export declare const displayModeStore: {
|
|
|
4
4
|
getServerSnapshot(): DisplayMode;
|
|
5
5
|
subscribe(listener: () => void): () => void;
|
|
6
6
|
setDisplayMode(nextDisplayMode: DisplayMode): void;
|
|
7
|
-
/** Test-only: reset back to the default so specs don't leak state. */
|
|
8
7
|
reset(): void;
|
|
9
8
|
};
|
|
10
|
-
export declare function useDisplayMode(): DisplayMode;
|