@replohq/sandbox-runtime 1.7.10 → 1.7.12

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.
Files changed (41) hide show
  1. package/_vendor/replo-utils/react/zustand.d.ts +11 -0
  2. package/_vendor/replo-utils/react/zustand.mjs +13 -0
  3. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +11 -8
  4. package/_vendor/schemas/selectModeIframeRpc.d.ts +66 -130
  5. package/_vendor/schemas/selectModeIframeRpc.mjs +16 -11
  6. package/buildMetadata.js +3 -3
  7. package/package.json +6 -5
  8. package/select-mode/EditorSelection.d.ts +1 -22
  9. package/select-mode/EditorSelection.js +61 -100
  10. package/select-mode/SelectionFrames.d.ts +1 -0
  11. package/select-mode/SelectionFrames.js +58 -0
  12. package/select-mode/annotationFrames.d.ts +13 -0
  13. package/select-mode/annotationFrames.js +31 -1
  14. package/select-mode/displayModeStore.d.ts +0 -2
  15. package/select-mode/displayModeStore.js +11 -30
  16. package/select-mode/hooks/useParentMessaging.js +3 -0
  17. package/select-mode/hooks/useSelectionHover.d.ts +1 -19
  18. package/select-mode/hooks/useSelectionHover.js +37 -49
  19. package/select-mode/hooks/useSelectionInteractions.d.ts +1 -12
  20. package/select-mode/hooks/useSelectionInteractions.js +57 -39
  21. package/select-mode/hooks/useSelectionScrollSync.d.ts +1 -25
  22. package/select-mode/hooks/useSelectionScrollSync.js +48 -40
  23. package/select-mode/iframeRpcChannel.d.ts +24 -1
  24. package/select-mode/inlineEditFeedback.d.ts +6 -0
  25. package/select-mode/inlineEditFeedback.js +47 -0
  26. package/select-mode/isTextEditableElement.d.ts +1 -0
  27. package/select-mode/isTextEditableElement.js +15 -0
  28. package/select-mode/selectModeStore.d.ts +160 -0
  29. package/select-mode/selectModeStore.js +586 -0
  30. package/select-mode/selectionFrame.d.ts +10 -59
  31. package/select-mode/selectionFrame.js +26 -150
  32. package/select-mode/symbolicate.js +4 -0
  33. package/select-mode/tiptapInlineEditor.js +16 -1
  34. package/select-mode/useShallowStore.d.ts +2 -0
  35. package/select-mode/useShallowStore.js +9 -0
  36. package/select-mode/hooks/useCrosshairCursor.d.ts +0 -10
  37. package/select-mode/hooks/useCrosshairCursor.js +0 -23
  38. package/select-mode/hooks/useEditModeStateMachine.d.ts +0 -16
  39. package/select-mode/hooks/useEditModeStateMachine.js +0 -228
  40. package/select-mode/hooks/useSelectModeController.d.ts +0 -11
  41. package/select-mode/hooks/useSelectModeController.js +0 -72
@@ -0,0 +1,586 @@
1
+ "use client";
2
+ import { createShallowStore } from "../_vendor/replo-utils/react/zustand.mjs";
3
+ import {
4
+ buildElementDescriptor,
5
+ buildSelector
6
+ } from "./descriptor/elementDescriptor";
7
+ import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
8
+ import {
9
+ getWebsiteBuilderRpc,
10
+ requestInlineTextEdit
11
+ } from "./iframeRpcChannel";
12
+ import { reportInlineEditFeedback } from "./inlineEditFeedback";
13
+ import { getDocumentScrollOffset } from "./interactions/selectionGeometry";
14
+ import { isTextEditableElement } from "./isTextEditableElement";
15
+ import {
16
+ getAreaSelectionFrameSpecs,
17
+ getElementSelectionFrameSpecs
18
+ } from "./selectionFrame";
19
+ import { getConnectedSelectedElement, IDLE_SESSION } from "./selectModeSession";
20
+ import {
21
+ isUserSourceLocation,
22
+ symbolicateSelectedElement
23
+ } from "./symbolicate";
24
+ import { createInlineTiptapEditor } from "./tiptapInlineEditor";
25
+ import { useShallowStore } from "./useShallowStore";
26
+ const EMPTY_ACTIVE_MARKS = {
27
+ bold: false,
28
+ italic: false,
29
+ underline: false,
30
+ strike: false
31
+ };
32
+ const FOCUS_STEAL_GUARD_MS = 1500;
33
+ function isPendingEdit({
34
+ editId,
35
+ request
36
+ }) {
37
+ return request?.status === "pending" && request.editId === editId;
38
+ }
39
+ function stopInlineEditRequest(request) {
40
+ if (!request) {
41
+ return;
42
+ }
43
+ if (request.status === "pending") {
44
+ clearTimeout(request.timeout);
45
+ }
46
+ request.stopFeedback();
47
+ }
48
+ function reportInlineEditingState({
49
+ activeMarks,
50
+ isInlineEditing,
51
+ request
52
+ }) {
53
+ getWebsiteBuilderRpc()?.editorStateUpdate({
54
+ activeMarks,
55
+ isInlineEditing,
56
+ isInlineEditPending: request?.status === "pending"
57
+ });
58
+ }
59
+ const INITIAL_STATE = {
60
+ displayMode: "browse",
61
+ interactionSession: IDLE_SESSION,
62
+ selectionFrames: [],
63
+ isSelectionFrameEditing: false,
64
+ lastPointerClientPosition: null,
65
+ lastPostedHoverRect: void 0,
66
+ hoverSyncAnimationFrameId: null,
67
+ lastClick: null,
68
+ dragCandidates: [],
69
+ committedSelectionAtPointerDown: [],
70
+ dragAnimationFrameId: null,
71
+ editingPointerIds: /* @__PURE__ */ new Set(),
72
+ scrollSyncAnimationFrameId: null,
73
+ inlineEditRequest: null,
74
+ editor: null,
75
+ editingElement: null,
76
+ editId: null,
77
+ originalInnerHTML: null,
78
+ initialHTML: null,
79
+ isCommitting: false,
80
+ beginEditingAt: 0,
81
+ activeMarks: EMPTY_ACTIVE_MARKS
82
+ };
83
+ const selectModeStore = createShallowStore(
84
+ (set, get) => {
85
+ const setInlineEditRequest = ({
86
+ activeMarks,
87
+ isInlineEditing,
88
+ next
89
+ }) => {
90
+ stopInlineEditRequest(get().inlineEditRequest);
91
+ if (!next) {
92
+ set({ inlineEditRequest: null });
93
+ reportInlineEditingState({
94
+ activeMarks,
95
+ isInlineEditing,
96
+ request: null
97
+ });
98
+ return;
99
+ }
100
+ const assigned = {
101
+ ...next,
102
+ stopFeedback: reportInlineEditFeedback({
103
+ element: next.element,
104
+ status: next.status,
105
+ onDisconnected: () => {
106
+ const current = get().inlineEditRequest;
107
+ if (next.status === "pending") {
108
+ if (!isPendingEdit({ editId: next.editId, request: current })) {
109
+ return;
110
+ }
111
+ } else if (current?.element !== next.element) {
112
+ return;
113
+ }
114
+ setInlineEditRequest({
115
+ activeMarks: EMPTY_ACTIVE_MARKS,
116
+ isInlineEditing: false,
117
+ next: null
118
+ });
119
+ }
120
+ })
121
+ };
122
+ set({ inlineEditRequest: assigned });
123
+ reportInlineEditingState({
124
+ activeMarks,
125
+ isInlineEditing,
126
+ request: assigned
127
+ });
128
+ };
129
+ const destroyEditor = () => {
130
+ const { editor } = get();
131
+ editor?.destroy();
132
+ set({
133
+ editor: null,
134
+ editingElement: null,
135
+ editId: null,
136
+ originalInnerHTML: null,
137
+ initialHTML: null,
138
+ isCommitting: false,
139
+ isSelectionFrameEditing: false,
140
+ activeMarks: EMPTY_ACTIVE_MARKS
141
+ });
142
+ setInlineEditRequest({
143
+ activeMarks: EMPTY_ACTIVE_MARKS,
144
+ isInlineEditing: false,
145
+ next: null
146
+ });
147
+ };
148
+ const revertInlineEdit = () => {
149
+ const { editingElement, originalInnerHTML } = get();
150
+ destroyEditor();
151
+ if (editingElement && originalInnerHTML !== null) {
152
+ editingElement.innerHTML = originalInnerHTML;
153
+ }
154
+ };
155
+ const commitInlineEdit = () => {
156
+ const {
157
+ editor,
158
+ editingElement,
159
+ originalInnerHTML,
160
+ editId,
161
+ isCommitting,
162
+ initialHTML
163
+ } = get();
164
+ if (!editor || !editId || originalInnerHTML === null || isCommitting) {
165
+ return;
166
+ }
167
+ set({ isCommitting: true });
168
+ const committedHTML = editor.getHTML();
169
+ if (committedHTML === initialHTML) {
170
+ destroyEditor();
171
+ if (editingElement) {
172
+ editingElement.innerHTML = originalInnerHTML;
173
+ }
174
+ return;
175
+ }
176
+ getWebsiteBuilderRpc()?.textCommitted({
177
+ editId,
178
+ html: committedHTML,
179
+ json: editor.getJSON(),
180
+ originalInnerHTML
181
+ });
182
+ destroyEditor();
183
+ if (editingElement) {
184
+ editingElement.innerHTML = committedHTML;
185
+ }
186
+ };
187
+ const handleBlur = () => {
188
+ const { isCommitting, beginEditingAt, editor } = get();
189
+ if (isCommitting) {
190
+ return;
191
+ }
192
+ if (typeof document !== "undefined" && document.hasFocus()) {
193
+ commitInlineEdit();
194
+ return;
195
+ }
196
+ if (Date.now() - beginEditingAt < FOCUS_STEAL_GUARD_MS) {
197
+ editor?.focus();
198
+ }
199
+ };
200
+ const finishPendingEdit = ({
201
+ editId,
202
+ status
203
+ }) => {
204
+ const pending = get().inlineEditRequest;
205
+ if (pending?.status !== "pending" || pending.editId !== editId) {
206
+ return;
207
+ }
208
+ if (!pending.element.isConnected) {
209
+ setInlineEditRequest({
210
+ activeMarks: EMPTY_ACTIVE_MARKS,
211
+ isInlineEditing: false,
212
+ next: null
213
+ });
214
+ return;
215
+ }
216
+ setInlineEditRequest({
217
+ activeMarks: EMPTY_ACTIVE_MARKS,
218
+ isInlineEditing: false,
219
+ next: {
220
+ status,
221
+ element: pending.element
222
+ }
223
+ });
224
+ };
225
+ const resolveEditRequest = ({
226
+ editId,
227
+ allowed
228
+ }) => {
229
+ const pending = get().inlineEditRequest;
230
+ if (pending?.status !== "pending" || pending.editId !== editId) {
231
+ return;
232
+ }
233
+ if (!allowed) {
234
+ finishPendingEdit({ editId, status: "unavailable" });
235
+ return;
236
+ }
237
+ if (!pending.element.isConnected) {
238
+ setInlineEditRequest({
239
+ activeMarks: EMPTY_ACTIVE_MARKS,
240
+ isInlineEditing: false,
241
+ next: null
242
+ });
243
+ return;
244
+ }
245
+ stopInlineEditRequest(pending);
246
+ set({ inlineEditRequest: null });
247
+ const element = pending.element;
248
+ const originalInnerHTML = element.innerHTML;
249
+ const editor = createInlineTiptapEditor({
250
+ element,
251
+ content: originalInnerHTML,
252
+ onCommit: commitInlineEdit,
253
+ onEscape: revertInlineEdit,
254
+ onBlur: handleBlur,
255
+ onSelectionUpdate: (activeMarks) => {
256
+ set({ activeMarks });
257
+ reportInlineEditingState({
258
+ activeMarks,
259
+ isInlineEditing: true,
260
+ request: null
261
+ });
262
+ }
263
+ });
264
+ set({
265
+ editor,
266
+ editingElement: element,
267
+ editId,
268
+ originalInnerHTML,
269
+ initialHTML: editor.getHTML(),
270
+ beginEditingAt: Date.now(),
271
+ isSelectionFrameEditing: true,
272
+ activeMarks: EMPTY_ACTIVE_MARKS
273
+ });
274
+ editor.focusAndSelectAll();
275
+ reportInlineEditingState({
276
+ activeMarks: EMPTY_ACTIVE_MARKS,
277
+ isInlineEditing: true,
278
+ request: null
279
+ });
280
+ };
281
+ const resetInteractionSession = () => {
282
+ const session = get().interactionSession;
283
+ if (session.type === "drag" && session.hasExceededThreshold) {
284
+ getWebsiteBuilderRpc()?.dragRectUpdated({
285
+ dragRect: null,
286
+ selectionRects: []
287
+ });
288
+ }
289
+ const hoverFrameId = get().hoverSyncAnimationFrameId;
290
+ if (hoverFrameId !== null) {
291
+ cancelAnimationFrame(hoverFrameId);
292
+ }
293
+ const dragFrameId = get().dragAnimationFrameId;
294
+ if (dragFrameId !== null) {
295
+ cancelAnimationFrame(dragFrameId);
296
+ }
297
+ set({
298
+ interactionSession: IDLE_SESSION,
299
+ selectionFrames: [],
300
+ lastPostedHoverRect: void 0,
301
+ hoverSyncAnimationFrameId: null,
302
+ dragAnimationFrameId: null,
303
+ dragCandidates: []
304
+ });
305
+ };
306
+ return {
307
+ ...INITIAL_STATE,
308
+ actions: {
309
+ reset: () => {
310
+ const state = get();
311
+ stopInlineEditRequest(state.inlineEditRequest);
312
+ state.editor?.destroy();
313
+ if (state.hoverSyncAnimationFrameId !== null) {
314
+ cancelAnimationFrame(state.hoverSyncAnimationFrameId);
315
+ }
316
+ if (state.dragAnimationFrameId !== null) {
317
+ cancelAnimationFrame(state.dragAnimationFrameId);
318
+ }
319
+ if (state.scrollSyncAnimationFrameId !== null) {
320
+ cancelAnimationFrame(state.scrollSyncAnimationFrameId);
321
+ }
322
+ set({
323
+ ...INITIAL_STATE,
324
+ editingPointerIds: /* @__PURE__ */ new Set(),
325
+ actions: state.actions
326
+ });
327
+ },
328
+ setDisplayMode: (displayMode) => {
329
+ if (get().displayMode === "selection" && displayMode !== "selection") {
330
+ resetInteractionSession();
331
+ revertInlineEdit();
332
+ }
333
+ set({ displayMode });
334
+ },
335
+ setSelectionType: ({ selectionType, selectors }) => {
336
+ if (selectionType === "none") {
337
+ resetInteractionSession();
338
+ return;
339
+ }
340
+ const session = get().interactionSession;
341
+ if (selectionType !== "area" || !selectors || session.type !== "area") {
342
+ return;
343
+ }
344
+ const retainedSelectors = new Set(selectors);
345
+ const items = session.items.filter((item) => {
346
+ return item.data.kind !== "react-element" || retainedSelectors.has(item.data.selector);
347
+ });
348
+ if (items.length === session.items.length) {
349
+ return;
350
+ }
351
+ if (items.length === 0) {
352
+ resetInteractionSession();
353
+ return;
354
+ }
355
+ set({ interactionSession: { ...session, items } });
356
+ get().actions.showAreaSelectionFrames({
357
+ elements: items.map((item) => item.element),
358
+ scrollOffset: getDocumentScrollOffset()
359
+ });
360
+ },
361
+ setInteractionSession: (interactionSession) => {
362
+ set({ interactionSession });
363
+ },
364
+ resetInteractionSession,
365
+ postSelectedElementSnapshot: ({ clearMissing = false } = {}) => {
366
+ const { interactionSession } = get();
367
+ if (interactionSession.type !== "element") return true;
368
+ const element = getConnectedSelectedElement(interactionSession);
369
+ if (!element) {
370
+ if (!clearMissing) return false;
371
+ get().actions.setSelectionType({ selectionType: "none" });
372
+ getWebsiteBuilderRpc()?.areaSelected({
373
+ rect: interactionSession.data.rect,
374
+ items: []
375
+ });
376
+ return true;
377
+ }
378
+ const siblingElements = getRepetitionSiblingElements({ element });
379
+ const descriptor = buildElementDescriptor({
380
+ element,
381
+ clickPosition: interactionSession.clickPosition ?? (interactionSession.data.kind === "react-element" ? interactionSession.data.clickPosition : { x: 0, y: 0 }),
382
+ siblingSelectors: siblingElements.map(buildSelector)
383
+ });
384
+ if (descriptor.kind !== "react-element") return true;
385
+ const refreshedSession = {
386
+ ...interactionSession,
387
+ element,
388
+ siblingElements
389
+ };
390
+ set({ interactionSession: refreshedSession });
391
+ get().actions.showElementSelectionFrames({
392
+ elements: [element, ...siblingElements],
393
+ scrollOffset: getDocumentScrollOffset()
394
+ });
395
+ void symbolicateSelectedElement(descriptor).then((symbolicated) => {
396
+ if (get().interactionSession !== refreshedSession) return;
397
+ if (symbolicated.sourceLocation && !isUserSourceLocation(symbolicated.sourceLocation))
398
+ return;
399
+ set({
400
+ interactionSession: { ...refreshedSession, data: symbolicated }
401
+ });
402
+ getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });
403
+ });
404
+ return true;
405
+ },
406
+ showElementSelectionFrames: ({ elements, scrollOffset }) => {
407
+ set({
408
+ selectionFrames: getElementSelectionFrameSpecs({
409
+ elements,
410
+ scrollOffset
411
+ })
412
+ });
413
+ },
414
+ showAreaSelectionFrames: ({ elements, scrollOffset }) => {
415
+ set({
416
+ selectionFrames: getAreaSelectionFrameSpecs({
417
+ elements,
418
+ scrollOffset
419
+ })
420
+ });
421
+ },
422
+ hideSelectionFrames: () => {
423
+ set({ selectionFrames: [] });
424
+ },
425
+ setLastPointerClientPosition: (lastPointerClientPosition) => {
426
+ set({ lastPointerClientPosition });
427
+ },
428
+ setLastPostedHoverRect: (lastPostedHoverRect) => {
429
+ set({ lastPostedHoverRect });
430
+ },
431
+ resetHoverRectDedup: () => {
432
+ set({ lastPostedHoverRect: void 0 });
433
+ },
434
+ setHoverSyncAnimationFrameId: (hoverSyncAnimationFrameId) => {
435
+ set({ hoverSyncAnimationFrameId });
436
+ },
437
+ setLastClick: (lastClick) => {
438
+ set({ lastClick });
439
+ },
440
+ setDragCandidates: (dragCandidates) => {
441
+ set({ dragCandidates });
442
+ },
443
+ setCommittedSelectionAtPointerDown: (committedSelectionAtPointerDown) => {
444
+ set({ committedSelectionAtPointerDown });
445
+ },
446
+ setDragAnimationFrameId: (dragAnimationFrameId) => {
447
+ set({ dragAnimationFrameId });
448
+ },
449
+ setScrollSyncAnimationFrameId: (scrollSyncAnimationFrameId) => {
450
+ set({ scrollSyncAnimationFrameId });
451
+ },
452
+ beginEditing: ({ element, descriptorPromise }) => {
453
+ const { editingElement, inlineEditRequest } = get();
454
+ if (editingElement === element) {
455
+ return;
456
+ }
457
+ if (!isTextEditableElement(element)) {
458
+ return;
459
+ }
460
+ if (inlineEditRequest?.status === "pending" && inlineEditRequest.element === element) {
461
+ return;
462
+ }
463
+ if (editingElement) {
464
+ revertInlineEdit();
465
+ }
466
+ const editId = crypto.randomUUID();
467
+ const timeout = window.setTimeout(
468
+ () => finishPendingEdit({ editId, status: "failed" }),
469
+ 5e3
470
+ );
471
+ setInlineEditRequest({
472
+ activeMarks: EMPTY_ACTIVE_MARKS,
473
+ isInlineEditing: false,
474
+ next: {
475
+ status: "pending",
476
+ editId,
477
+ element,
478
+ timeout
479
+ }
480
+ });
481
+ void descriptorPromise.then(async (descriptor) => {
482
+ if (!isPendingEdit({
483
+ editId,
484
+ request: get().inlineEditRequest
485
+ })) {
486
+ return;
487
+ }
488
+ if (!element.isConnected) {
489
+ setInlineEditRequest({
490
+ activeMarks: EMPTY_ACTIVE_MARKS,
491
+ isInlineEditing: false,
492
+ next: null
493
+ });
494
+ return;
495
+ }
496
+ if (descriptor.kind !== "react-element") {
497
+ finishPendingEdit({ editId, status: "unavailable" });
498
+ return;
499
+ }
500
+ get().actions.setInteractionSession({
501
+ type: "element",
502
+ element,
503
+ siblingElements: getRepetitionSiblingElements({ element }),
504
+ clickPosition: null,
505
+ data: descriptor
506
+ });
507
+ const symbolicated = await symbolicateSelectedElement(descriptor);
508
+ if (!isPendingEdit({
509
+ editId,
510
+ request: get().inlineEditRequest
511
+ })) {
512
+ return;
513
+ }
514
+ const { allowed } = await requestInlineTextEdit({
515
+ editId,
516
+ element: symbolicated
517
+ });
518
+ resolveEditRequest({ editId, allowed });
519
+ }).catch(() => {
520
+ if (get().editId === editId) {
521
+ revertInlineEdit();
522
+ } else {
523
+ finishPendingEdit({ editId, status: "failed" });
524
+ }
525
+ });
526
+ },
527
+ commitInlineEdit,
528
+ revertInlineEdit,
529
+ formatCommand: (command) => {
530
+ get().editor?.runFormatCommand(command);
531
+ },
532
+ dismissInlineEditRequest: () => {
533
+ if (!get().inlineEditRequest) {
534
+ return;
535
+ }
536
+ setInlineEditRequest({
537
+ activeMarks: EMPTY_ACTIVE_MARKS,
538
+ isInlineEditing: Boolean(get().editor),
539
+ next: null
540
+ });
541
+ }
542
+ }
543
+ };
544
+ }
545
+ );
546
+ function useSelectModeStore() {
547
+ return selectModeStore;
548
+ }
549
+ const useSelectModeStoreActions = () => {
550
+ return useShallowStore(useSelectModeStore(), (state) => state.actions);
551
+ };
552
+ const useDisplayMode = () => {
553
+ return useShallowStore(useSelectModeStore(), (state) => state.displayMode);
554
+ };
555
+ const useSelectionFrames = () => {
556
+ return useShallowStore(
557
+ useSelectModeStore(),
558
+ (state) => state.selectionFrames
559
+ );
560
+ };
561
+ const useIsSelectionFrameEditing = () => {
562
+ return useShallowStore(
563
+ useSelectModeStore(),
564
+ (state) => state.isSelectionFrameEditing
565
+ );
566
+ };
567
+ const useEditingElement = () => {
568
+ return useShallowStore(useSelectModeStore(), (state) => state.editingElement);
569
+ };
570
+ const useInteractionSession = () => {
571
+ return useShallowStore(
572
+ useSelectModeStore(),
573
+ (state) => state.interactionSession
574
+ );
575
+ };
576
+ export {
577
+ EMPTY_ACTIVE_MARKS,
578
+ selectModeStore,
579
+ useDisplayMode,
580
+ useEditingElement,
581
+ useInteractionSession,
582
+ useIsSelectionFrameEditing,
583
+ useSelectModeStore,
584
+ useSelectModeStoreActions,
585
+ useSelectionFrames
586
+ };
@@ -1,77 +1,28 @@
1
- /**
2
- * In-iframe selection frame renderer.
3
- *
4
- * Lives in the canopy iframe (not in agent-ui) because the previous
5
- * parent-side overlay had to be re-anchored on every scroll frame via
6
- * postMessage hop -> Zustand update -> React reconciliation, which
7
- * shows up as visible lag when the user scrolls quickly.
8
- *
9
- * By rendering directly inside the canopy iframe's document we get two
10
- * properties for free:
11
- *
12
- * 1. `position: absolute` + page coordinates means the document's own
13
- * scroll naturally translates the frames — zero JavaScript per
14
- * scroll frame, the browser composites them with the underlying page
15
- * content.
16
- * 2. No bridge serialization: the only cost when selection commits is
17
- * a handful of style writes on the frame nodes.
18
- *
19
- * A click resolves to one frame per repeated element; an area drag resolves
20
- * to one frame per captured element. The renderer keeps a pool of nodes under
21
- * a shared container and reconciles it on each call.
22
- *
23
- * The canopy iframe loads a customer page and doesn't include lattice /
24
- * tailwind, so styling is intentionally vanilla inline CSS rather than
25
- * Tailwind classes.
26
- */
27
- type SelectionFrameVariant = "element" | "area";
28
- type SelectionFramePageRect = {
1
+ export declare const SELECTION_FRAMES_CONTAINER_ID = "replo-select-mode-selection-frames";
2
+ export declare const SELECTION_FRAME_BORDER_HEX = "#c026d3";
3
+ export declare const SELECTION_FRAME_EDITING_BORDER_HEX = "#e879f9";
4
+ export type SelectionFrameVariant = "element" | "area";
5
+ export type SelectionFramePageRect = {
29
6
  x: number;
30
7
  y: number;
31
8
  width: number;
32
9
  height: number;
33
10
  };
34
- type SelectionFrameSpec = {
11
+ export type SelectionFrameSpec = {
35
12
  pageRect: SelectionFramePageRect;
36
13
  variant: SelectionFrameVariant;
37
14
  };
38
- /**
39
- * Toggle the brighter inline-edit border and re-color any frames already on
40
- * screen so the change is immediate, not deferred to the next redraw.
41
- */
42
- export declare function setSelectionFrameEditing(editing: boolean): void;
43
- /**
44
- * Shared body-level layer container for in-iframe editor overlays (selection
45
- * frames, annotation frames). Zero-size and pointer-transparent so it never
46
- * affects the page; children opt into pointer events individually.
47
- */
48
- export declare function getOrCreateFramesLayerContainer({ id, zIndex, }: {
49
- id: string;
50
- zIndex: number;
51
- }): HTMLDivElement | null;
52
- /**
53
- * Position selection frames at the given *page* coordinates, one node per
54
- * spec. Page coords don't shift during scroll, so the caller is free to
55
- * call this once at commit time and let the browser handle the rest of the
56
- * scroll lifetime. Reconciles the node pool against `frames.length` so group
57
- * updates reuse the existing nodes when the repetition count stays stable.
58
- */
59
- export declare function showSelectionFrames({ frames, }: {
60
- frames: SelectionFrameSpec[];
61
- }): void;
62
- export declare function showElementSelectionFrames({ elements, scrollOffset, }: {
15
+ export declare function getElementSelectionFrameSpecs({ elements, scrollOffset, }: {
63
16
  elements: Element[];
64
17
  scrollOffset: {
65
18
  left: number;
66
19
  top: number;
67
20
  };
68
- }): void;
69
- export declare function showAreaSelectionFrames({ elements, scrollOffset, }: {
21
+ }): SelectionFrameSpec[];
22
+ export declare function getAreaSelectionFrameSpecs({ elements, scrollOffset, }: {
70
23
  elements: Element[];
71
24
  scrollOffset: {
72
25
  left: number;
73
26
  top: number;
74
27
  };
75
- }): void;
76
- export declare function hideSelectionFrame(): void;
77
- export {};
28
+ }): SelectionFrameSpec[];