@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.
Files changed (139) hide show
  1. package/ClientErrorBoundary.d.ts +10 -0
  2. package/ClientErrorBoundary.js +61 -0
  3. package/ClientErrorBoundary.js.map +7 -0
  4. package/DocumentReadyBeacon.d.ts +1 -0
  5. package/DocumentReadyBeacon.js +9 -0
  6. package/DocumentReadyBeacon.js.map +7 -0
  7. package/LICENSE +11 -0
  8. package/LinkInterceptor.d.ts +1 -0
  9. package/LinkInterceptor.js +128 -0
  10. package/LinkInterceptor.js.map +7 -0
  11. package/NextErrorReporter.d.ts +1 -0
  12. package/NextErrorReporter.js +92 -0
  13. package/NextErrorReporter.js.map +7 -0
  14. package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
  15. package/_vendor/replo-utils/lib/errors.d.ts +83 -0
  16. package/_vendor/replo-utils/lib/fonts.d.ts +49 -0
  17. package/_vendor/replo-utils/lib/fonts.mjs +221 -0
  18. package/_vendor/replo-utils/lib/geometry.d.ts +18 -0
  19. package/_vendor/replo-utils/lib/geometry.mjs +9 -0
  20. package/_vendor/replo-utils/lib/json.d.ts +6 -0
  21. package/_vendor/replo-utils/lib/log.d.ts +11 -0
  22. package/_vendor/replo-utils/lib/misc.d.ts +145 -0
  23. package/_vendor/replo-utils/lib/misc.mjs +2 -0
  24. package/_vendor/replo-utils/lib/object.d.ts +31 -0
  25. package/_vendor/replo-utils/lib/parse.d.ts +15 -0
  26. package/_vendor/replo-utils/lib/posthogReplay.d.ts +43 -0
  27. package/_vendor/replo-utils/lib/posthogReplay.mjs +121 -0
  28. package/_vendor/replo-utils/react/use-iframe-rpc-channel.d.ts +14 -0
  29. package/_vendor/replo-utils/react/use-iframe-rpc-channel.mjs +14 -0
  30. package/_vendor/schemas/designLibrary.d.ts +361 -0
  31. package/_vendor/schemas/designLibrary.mjs +126 -0
  32. package/_vendor/schemas/errors.d.ts +459 -0
  33. package/_vendor/schemas/generated/errors.d.ts +61 -0
  34. package/_vendor/schemas/generated/pageAnnotations.d.ts +48 -0
  35. package/_vendor/schemas/generated/pageAnnotations.mjs +1 -0
  36. package/_vendor/schemas/generated/selectMode.d.ts +119 -0
  37. package/_vendor/schemas/generated/selectMode.mjs +1 -0
  38. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +100 -0
  39. package/_vendor/schemas/generated/selectModeIframeRpc.mjs +1 -0
  40. package/_vendor/schemas/iframeRpcChannel.d.ts +82 -0
  41. package/_vendor/schemas/iframeRpcChannel.mjs +520 -0
  42. package/_vendor/schemas/pageAnnotations.d.ts +134 -0
  43. package/_vendor/schemas/pageAnnotations.mjs +61 -0
  44. package/_vendor/schemas/savedStyles.d.ts +140 -0
  45. package/_vendor/schemas/selectMode.d.ts +522 -0
  46. package/_vendor/schemas/selectMode.mjs +3 -0
  47. package/_vendor/schemas/selectModeIframeRpc.d.ts +900 -0
  48. package/_vendor/schemas/selectModeIframeRpc.mjs +223 -0
  49. package/annotations/AnnotationLayerMount.d.ts +1 -0
  50. package/annotations/AnnotationLayerMount.js +13 -0
  51. package/annotations/AnnotationLayerMount.js.map +7 -0
  52. package/annotations/annotationController.d.ts +1 -0
  53. package/annotations/annotationController.js +384 -0
  54. package/annotations/annotationController.js.map +7 -0
  55. package/annotations/bridgeGate.d.ts +7 -0
  56. package/annotations/bridgeGate.js +22 -0
  57. package/annotations/bridgeGate.js.map +7 -0
  58. package/chunk-3UFG7XTQ.mjs +205 -0
  59. package/chunk-FNTGY7I5.mjs +68 -0
  60. package/chunk-N5EI2MOZ.mjs +1535 -0
  61. package/design-library-preview/DesignLibraryPreview.d.ts +1 -0
  62. package/design-library-preview/DesignLibraryPreview.js +93 -0
  63. package/design-library-preview/DesignLibraryPreview.js.map +7 -0
  64. package/package.json +41 -0
  65. package/posthog-relay.d.ts +11 -0
  66. package/posthog-relay.js +72 -0
  67. package/posthog-relay.js.map +7 -0
  68. package/sandbox-runtime.d.ts +1 -0
  69. package/sandbox-runtime.js +29 -0
  70. package/sandbox-runtime.js.map +7 -0
  71. package/select-mode/EditorSelection.d.ts +24 -0
  72. package/select-mode/EditorSelection.js +95 -0
  73. package/select-mode/EditorSelection.js.map +7 -0
  74. package/select-mode/annotationFrames.d.ts +33 -0
  75. package/select-mode/annotationFrames.js +119 -0
  76. package/select-mode/annotationFrames.js.map +7 -0
  77. package/select-mode/descriptor/elementDescriptor.d.ts +26 -0
  78. package/select-mode/descriptor/elementDescriptor.js +201 -0
  79. package/select-mode/descriptor/elementDescriptor.js.map +7 -0
  80. package/select-mode/descriptor/styleExtraction.d.ts +3 -0
  81. package/select-mode/descriptor/styleExtraction.js +271 -0
  82. package/select-mode/descriptor/styleExtraction.js.map +7 -0
  83. package/select-mode/displayModeStore.d.ts +10 -0
  84. package/select-mode/displayModeStore.js +47 -0
  85. package/select-mode/displayModeStore.js.map +7 -0
  86. package/select-mode/fast-refresh-relay.d.ts +3 -0
  87. package/select-mode/fast-refresh-relay.js +33 -0
  88. package/select-mode/fast-refresh-relay.js.map +7 -0
  89. package/select-mode/fiber.d.ts +49 -0
  90. package/select-mode/fiber.js +306 -0
  91. package/select-mode/fiber.js.map +7 -0
  92. package/select-mode/hooks/useCrosshairCursor.d.ts +12 -0
  93. package/select-mode/hooks/useCrosshairCursor.js +20 -0
  94. package/select-mode/hooks/useCrosshairCursor.js.map +7 -0
  95. package/select-mode/hooks/useEditModeStateMachine.d.ts +16 -0
  96. package/select-mode/hooks/useEditModeStateMachine.js +235 -0
  97. package/select-mode/hooks/useEditModeStateMachine.js.map +7 -0
  98. package/select-mode/hooks/useOptimisticOverrides.d.ts +17 -0
  99. package/select-mode/hooks/useOptimisticOverrides.js +283 -0
  100. package/select-mode/hooks/useOptimisticOverrides.js.map +7 -0
  101. package/select-mode/hooks/useParentMessaging.d.ts +36 -0
  102. package/select-mode/hooks/useParentMessaging.js +74 -0
  103. package/select-mode/hooks/useParentMessaging.js.map +7 -0
  104. package/select-mode/hooks/useSelectModeController.d.ts +8 -0
  105. package/select-mode/hooks/useSelectModeController.js +46 -0
  106. package/select-mode/hooks/useSelectModeController.js.map +7 -0
  107. package/select-mode/hooks/useSelectionHover.d.ts +22 -0
  108. package/select-mode/hooks/useSelectionHover.js +110 -0
  109. package/select-mode/hooks/useSelectionHover.js.map +7 -0
  110. package/select-mode/hooks/useSelectionInteractions.d.ts +37 -0
  111. package/select-mode/hooks/useSelectionInteractions.js +353 -0
  112. package/select-mode/hooks/useSelectionInteractions.js.map +7 -0
  113. package/select-mode/hooks/useSelectionScrollSync.d.ts +28 -0
  114. package/select-mode/hooks/useSelectionScrollSync.js +70 -0
  115. package/select-mode/hooks/useSelectionScrollSync.js.map +7 -0
  116. package/select-mode/hooks/useSiblingResolution.d.ts +6 -0
  117. package/select-mode/hooks/useSiblingResolution.js +59 -0
  118. package/select-mode/hooks/useSiblingResolution.js.map +7 -0
  119. package/select-mode/iframeRpcChannel.d.ts +356 -0
  120. package/select-mode/iframeRpcChannel.js +92 -0
  121. package/select-mode/iframeRpcChannel.js.map +7 -0
  122. package/select-mode/inlineTextMarks.d.ts +6 -0
  123. package/select-mode/inlineTextMarks.js +25 -0
  124. package/select-mode/inlineTextMarks.js.map +7 -0
  125. package/select-mode/interactions/selectionGeometry.d.ts +105 -0
  126. package/select-mode/interactions/selectionGeometry.js +192 -0
  127. package/select-mode/interactions/selectionGeometry.js.map +7 -0
  128. package/select-mode/selectModeSession.d.ts +21 -0
  129. package/select-mode/selectModeSession.js +7 -0
  130. package/select-mode/selectModeSession.js.map +7 -0
  131. package/select-mode/selectionFrame.d.ts +81 -0
  132. package/select-mode/selectionFrame.js +132 -0
  133. package/select-mode/selectionFrame.js.map +7 -0
  134. package/select-mode/symbolicate.d.ts +2 -0
  135. package/select-mode/symbolicate.js +200 -0
  136. package/select-mode/symbolicate.js.map +7 -0
  137. package/select-mode/tiptapInlineEditor.d.ts +23 -0
  138. package/select-mode/tiptapInlineEditor.js +120 -0
  139. package/select-mode/tiptapInlineEditor.js.map +7 -0
@@ -0,0 +1,235 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import {
3
+ getWebsiteBuilderRpc,
4
+ requestInlineTextEdit
5
+ } from "../iframeRpcChannel";
6
+ import { setSelectionFrameEditing } from "../selectionFrame";
7
+ import { symbolicateSelectedElement } from "../symbolicate";
8
+ const EMPTY_ACTIVE_MARKS = {
9
+ bold: false,
10
+ italic: false,
11
+ underline: false,
12
+ strike: false
13
+ };
14
+ function postEditorState({
15
+ activeMarks,
16
+ isEditing
17
+ }) {
18
+ getWebsiteBuilderRpc()?.editorStateUpdate({ activeMarks, isEditing });
19
+ }
20
+ function isTextEditableElement(element) {
21
+ const tagName = element.tagName.toLowerCase();
22
+ return element instanceof HTMLElement && Boolean(element.textContent?.trim()) && ![
23
+ "input",
24
+ "textarea",
25
+ "select",
26
+ "option",
27
+ "img",
28
+ "video",
29
+ "canvas"
30
+ ].includes(tagName);
31
+ }
32
+ const FOCUS_STEAL_GUARD_MS = 1500;
33
+ function useEditModeStateMachine({
34
+ displayMode,
35
+ interactionSessionRef
36
+ }) {
37
+ const editorRef = useRef(null);
38
+ const editingElementRef = useRef(null);
39
+ const editIdRef = useRef(null);
40
+ const pendingEditRef = useRef(null);
41
+ const originalInnerHTMLRef = useRef(null);
42
+ const initialHTMLRef = useRef(null);
43
+ const isCommittingRef = useRef(false);
44
+ const beginEditingAtRef = useRef(0);
45
+ const destroyEditor = useCallback(() => {
46
+ editorRef.current?.destroy();
47
+ editorRef.current = null;
48
+ editingElementRef.current = null;
49
+ editIdRef.current = null;
50
+ if (pendingEditRef.current) {
51
+ clearTimeout(pendingEditRef.current.timeout);
52
+ pendingEditRef.current = null;
53
+ }
54
+ originalInnerHTMLRef.current = null;
55
+ initialHTMLRef.current = null;
56
+ isCommittingRef.current = false;
57
+ setSelectionFrameEditing(false);
58
+ postEditorState({ activeMarks: EMPTY_ACTIVE_MARKS, isEditing: false });
59
+ }, []);
60
+ const commit = useCallback(() => {
61
+ const editor = editorRef.current;
62
+ const editingElement = editingElementRef.current;
63
+ const originalInnerHTML = originalInnerHTMLRef.current;
64
+ const editId = editIdRef.current;
65
+ if (!editor || !editId || originalInnerHTML === null || isCommittingRef.current) {
66
+ return;
67
+ }
68
+ isCommittingRef.current = true;
69
+ const committedHTML = editor.getHTML();
70
+ if (committedHTML === initialHTMLRef.current) {
71
+ destroyEditor();
72
+ if (editingElement) {
73
+ editingElement.innerHTML = originalInnerHTML;
74
+ }
75
+ return;
76
+ }
77
+ const committedJSON = editor.getJSON();
78
+ getWebsiteBuilderRpc()?.textCommitted({
79
+ editId,
80
+ html: committedHTML,
81
+ json: committedJSON,
82
+ originalInnerHTML
83
+ });
84
+ destroyEditor();
85
+ if (editingElement) {
86
+ editingElement.innerHTML = committedHTML;
87
+ }
88
+ }, [destroyEditor]);
89
+ const revert = useCallback(() => {
90
+ const editingElement = editingElementRef.current;
91
+ const originalInnerHTML = originalInnerHTMLRef.current;
92
+ destroyEditor();
93
+ if (editingElement && originalInnerHTML !== null) {
94
+ editingElement.innerHTML = originalInnerHTML;
95
+ }
96
+ }, [destroyEditor]);
97
+ const handleBlur = useCallback(() => {
98
+ if (isCommittingRef.current) {
99
+ return;
100
+ }
101
+ if (typeof document !== "undefined" && document.hasFocus()) {
102
+ commit();
103
+ return;
104
+ }
105
+ if (Date.now() - beginEditingAtRef.current < FOCUS_STEAL_GUARD_MS) {
106
+ editorRef.current?.focus();
107
+ }
108
+ }, [commit]);
109
+ const formatCommand = useCallback((command) => {
110
+ editorRef.current?.runFormatCommand(command);
111
+ }, []);
112
+ const clearPendingEdit = useCallback((editId) => {
113
+ if (pendingEditRef.current?.editId !== editId) {
114
+ return;
115
+ }
116
+ clearTimeout(pendingEditRef.current.timeout);
117
+ pendingEditRef.current = null;
118
+ }, []);
119
+ const resolveEditRequest = useCallback(
120
+ ({ editId, allowed }) => {
121
+ const pendingEdit = pendingEditRef.current;
122
+ if (!pendingEdit || pendingEdit.editId !== editId) {
123
+ return;
124
+ }
125
+ clearTimeout(pendingEdit.timeout);
126
+ pendingEditRef.current = null;
127
+ if (!allowed || !pendingEdit.element.isConnected) {
128
+ return;
129
+ }
130
+ const element = pendingEdit.element;
131
+ const originalInnerHTML = element.innerHTML;
132
+ editingElementRef.current = element;
133
+ editIdRef.current = editId;
134
+ originalInnerHTMLRef.current = originalInnerHTML;
135
+ void import("../tiptapInlineEditor").then(({ createInlineTiptapEditor }) => {
136
+ if (editingElementRef.current !== element || editIdRef.current !== editId) {
137
+ return;
138
+ }
139
+ editorRef.current = createInlineTiptapEditor({
140
+ element,
141
+ content: originalInnerHTML,
142
+ onCommit: commit,
143
+ onEscape: revert,
144
+ onBlur: handleBlur,
145
+ onSelectionUpdate: (activeMarks) => {
146
+ postEditorState({ activeMarks, isEditing: true });
147
+ }
148
+ });
149
+ initialHTMLRef.current = editorRef.current.getHTML();
150
+ beginEditingAtRef.current = Date.now();
151
+ editorRef.current.focusAndSelectAll();
152
+ setSelectionFrameEditing(true);
153
+ postEditorState({ activeMarks: EMPTY_ACTIVE_MARKS, isEditing: true });
154
+ }).catch(() => {
155
+ if (editingElementRef.current === element && editIdRef.current === editId) {
156
+ revert();
157
+ }
158
+ });
159
+ },
160
+ [commit, handleBlur, revert]
161
+ );
162
+ const beginEditing = useCallback(
163
+ ({
164
+ element,
165
+ descriptorPromise
166
+ }) => {
167
+ if (editingElementRef.current === element) {
168
+ return;
169
+ }
170
+ if (!isTextEditableElement(element)) {
171
+ return;
172
+ }
173
+ if (pendingEditRef.current?.element === element) {
174
+ return;
175
+ }
176
+ void descriptorPromise.then((descriptor) => {
177
+ if (descriptor.kind !== "react-element" || !element.isConnected) {
178
+ return;
179
+ }
180
+ interactionSessionRef.current = {
181
+ type: "element",
182
+ element,
183
+ clickPosition: null,
184
+ data: descriptor
185
+ };
186
+ const editId = crypto.randomUUID();
187
+ if (pendingEditRef.current) {
188
+ clearTimeout(pendingEditRef.current.timeout);
189
+ }
190
+ const timeout = window.setTimeout(() => {
191
+ clearPendingEdit(editId);
192
+ }, 5e3);
193
+ pendingEditRef.current = { editId, element, timeout };
194
+ void symbolicateSelectedElement(descriptor).then(
195
+ (symbolicated) => {
196
+ if (pendingEditRef.current?.editId !== editId) {
197
+ return;
198
+ }
199
+ void requestInlineTextEdit({
200
+ editId,
201
+ element: symbolicated
202
+ }).then(
203
+ ({ allowed }) => {
204
+ resolveEditRequest({ editId, allowed });
205
+ },
206
+ () => {
207
+ resolveEditRequest({ editId, allowed: false });
208
+ }
209
+ );
210
+ },
211
+ () => {
212
+ clearPendingEdit(editId);
213
+ }
214
+ );
215
+ });
216
+ },
217
+ [clearPendingEdit, interactionSessionRef, resolveEditRequest]
218
+ );
219
+ useEffect(() => {
220
+ if (displayMode !== "selection") {
221
+ revert();
222
+ }
223
+ }, [displayMode, revert]);
224
+ return {
225
+ beginEditing,
226
+ editingElementRef,
227
+ formatCommand,
228
+ revert
229
+ };
230
+ }
231
+ export {
232
+ isTextEditableElement,
233
+ useEditModeStateMachine
234
+ };
235
+ //# sourceMappingURL=useEditModeStateMachine.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../select-mode/hooks/useEditModeStateMachine.ts"],
4
+ "sourcesContent": ["import type { MutableRefObject } from \"react\";\nimport type {\n DisplayMode,\n SelectableData,\n SelectModeFormatCommand,\n} from \"schemas/generated/selectMode\";\nimport type { SelectModeInteractionSession } from \"../selectModeSession\";\nimport type { InlineTiptapEditor } from \"../tiptapInlineEditor\";\n\nimport { useCallback, useEffect, useRef } from \"react\";\n\nimport {\n getWebsiteBuilderRpc,\n requestInlineTextEdit,\n} from \"../iframeRpcChannel\";\nimport { setSelectionFrameEditing } from \"../selectionFrame\";\nimport { symbolicateSelectedElement } from \"../symbolicate\";\n\ntype ActiveMarks = {\n bold: boolean;\n italic: boolean;\n underline: boolean;\n strike: boolean;\n};\n\nconst EMPTY_ACTIVE_MARKS: ActiveMarks = {\n bold: false,\n italic: false,\n underline: false,\n strike: false,\n};\n\n// Notify the parent of the inline editor's state so it can show the inline\n// formatting controls (and live mark state) only while editing.\nfunction postEditorState({\n activeMarks,\n isEditing,\n}: {\n activeMarks: ActiveMarks;\n isEditing: boolean;\n}) {\n getWebsiteBuilderRpc()?.editorStateUpdate({ activeMarks, isEditing });\n}\n\nexport function isTextEditableElement(\n element: Element,\n): element is HTMLElement {\n const tagName = element.tagName.toLowerCase();\n return (\n element instanceof HTMLElement &&\n Boolean(element.textContent?.trim()) &&\n ![\n \"input\",\n \"textarea\",\n \"select\",\n \"option\",\n \"img\",\n \"video\",\n \"canvas\",\n ].includes(tagName)\n );\n}\n\n// Window after entering an edit during which a focus loss to the parent frame\n// is treated as the select-mode panel's composer auto-focus stealing focus\n// (and is reclaimed) rather than a deliberate move into the editor UI.\nconst FOCUS_STEAL_GUARD_MS = 1500;\n\nexport function useEditModeStateMachine({\n displayMode,\n interactionSessionRef,\n}: {\n displayMode: DisplayMode;\n interactionSessionRef: MutableRefObject<SelectModeInteractionSession>;\n}) {\n const editorRef = useRef<InlineTiptapEditor | null>(null);\n const editingElementRef = useRef<HTMLElement | null>(null);\n const editIdRef = useRef<string | null>(null);\n const pendingEditRef = useRef<{\n editId: string;\n element: HTMLElement;\n timeout: number;\n } | null>(null);\n const originalInnerHTMLRef = useRef<string | null>(null);\n // The editor's HTML right after it parsed the original content. tiptap\n // re-serializes the markup, so this \u2014 not the raw `originalInnerHTML` \u2014 is the\n // apples-to-apples baseline for detecting whether a commit actually changed\n // anything.\n const initialHTMLRef = useRef<string | null>(null);\n const isCommittingRef = useRef(false);\n const beginEditingAtRef = useRef(0);\n\n const destroyEditor = useCallback(() => {\n editorRef.current?.destroy();\n editorRef.current = null;\n editingElementRef.current = null;\n editIdRef.current = null;\n if (pendingEditRef.current) {\n clearTimeout(pendingEditRef.current.timeout);\n pendingEditRef.current = null;\n }\n originalInnerHTMLRef.current = null;\n initialHTMLRef.current = null;\n isCommittingRef.current = false;\n setSelectionFrameEditing(false);\n postEditorState({ activeMarks: EMPTY_ACTIVE_MARKS, isEditing: false });\n }, []);\n\n const commit = useCallback(() => {\n const editor = editorRef.current;\n const editingElement = editingElementRef.current;\n const originalInnerHTML = originalInnerHTMLRef.current;\n const editId = editIdRef.current;\n if (\n !editor ||\n !editId ||\n originalInnerHTML === null ||\n isCommittingRef.current\n ) {\n return;\n }\n\n isCommittingRef.current = true;\n const committedHTML = editor.getHTML();\n\n // A blur or Shift-Enter with no actual change must not spawn an edit\n // session. When the content is untouched, restore the original markup and\n // tear down without notifying the parent.\n if (committedHTML === initialHTMLRef.current) {\n destroyEditor();\n if (editingElement) {\n editingElement.innerHTML = originalInnerHTML;\n }\n return;\n }\n\n const committedJSON = editor.getJSON();\n getWebsiteBuilderRpc()?.textCommitted({\n editId,\n html: committedHTML,\n json: committedJSON,\n originalInnerHTML,\n });\n // NOTE (Noah Gilmore, 2026-07-16): A directly mounted ProseMirror view\n // clears its root during destroy, so final HTML must be restored afterward.\n destroyEditor();\n if (editingElement) {\n editingElement.innerHTML = committedHTML;\n }\n }, [destroyEditor]);\n\n const revert = useCallback(() => {\n const editingElement = editingElementRef.current;\n const originalInnerHTML = originalInnerHTMLRef.current;\n destroyEditor();\n if (editingElement && originalInnerHTML !== null) {\n editingElement.innerHTML = originalInnerHTML;\n }\n }, [destroyEditor]);\n\n const handleBlur = useCallback(() => {\n if (isCommittingRef.current) {\n return;\n }\n // Focus stayed inside the iframe \u2014 the user clicked elsewhere on the page,\n // so this is a real \"click away\": commit.\n if (typeof document !== \"undefined\" && document.hasFocus()) {\n commit();\n return;\n }\n // Focus left the iframe to the parent editor. When it happens right as the\n // edit opens, the select-mode panel's composer auto-focus stole it \u2014 pull\n // it back so a double-click keeps the caret in the text. A deliberate move\n // into the editor UI later is left alone, and never commits/destroys.\n if (Date.now() - beginEditingAtRef.current < FOCUS_STEAL_GUARD_MS) {\n editorRef.current?.focus();\n }\n }, [commit]);\n\n const formatCommand = useCallback((command: SelectModeFormatCommand) => {\n editorRef.current?.runFormatCommand(command);\n }, []);\n\n const clearPendingEdit = useCallback((editId: string) => {\n if (pendingEditRef.current?.editId !== editId) {\n return;\n }\n clearTimeout(pendingEditRef.current.timeout);\n pendingEditRef.current = null;\n }, []);\n\n const resolveEditRequest = useCallback(\n ({ editId, allowed }: { editId: string; allowed: boolean }) => {\n const pendingEdit = pendingEditRef.current;\n if (!pendingEdit || pendingEdit.editId !== editId) {\n return;\n }\n clearTimeout(pendingEdit.timeout);\n pendingEditRef.current = null;\n if (!allowed || !pendingEdit.element.isConnected) {\n return;\n }\n\n const element = pendingEdit.element;\n const originalInnerHTML = element.innerHTML;\n editingElementRef.current = element;\n editIdRef.current = editId;\n originalInnerHTMLRef.current = originalInnerHTML;\n\n void import(\"../tiptapInlineEditor\")\n .then(({ createInlineTiptapEditor }) => {\n if (\n editingElementRef.current !== element ||\n editIdRef.current !== editId\n ) {\n return;\n }\n editorRef.current = createInlineTiptapEditor({\n element,\n content: originalInnerHTML,\n onCommit: commit,\n onEscape: revert,\n onBlur: handleBlur,\n onSelectionUpdate: (activeMarks) => {\n postEditorState({ activeMarks, isEditing: true });\n },\n });\n initialHTMLRef.current = editorRef.current.getHTML();\n beginEditingAtRef.current = Date.now();\n editorRef.current.focusAndSelectAll();\n setSelectionFrameEditing(true);\n postEditorState({ activeMarks: EMPTY_ACTIVE_MARKS, isEditing: true });\n })\n .catch(() => {\n if (\n editingElementRef.current === element &&\n editIdRef.current === editId\n ) {\n revert();\n }\n });\n },\n [commit, handleBlur, revert],\n );\n\n const beginEditing = useCallback(\n ({\n element,\n descriptorPromise,\n }: {\n element: Element;\n descriptorPromise: Promise<SelectableData>;\n }) => {\n // Already editing this element \u2014 re-entering would re-read the mid-edit\n // DOM as the \"original\" and rebuild the editor over itself, wiping the\n // content. The pointer-up guard prevents the usual trigger; this is the\n // backstop for any other path.\n if (editingElementRef.current === element) {\n return;\n }\n if (!isTextEditableElement(element)) {\n return;\n }\n // Same element already awaiting symbolicate / parent allow \u2014 ignore.\n // A different element supersedes the in-flight attempt below.\n if (pendingEditRef.current?.element === element) {\n return;\n }\n\n void descriptorPromise.then((descriptor) => {\n if (descriptor.kind !== \"react-element\" || !element.isConnected) {\n return;\n }\n interactionSessionRef.current = {\n type: \"element\",\n element,\n clickPosition: null,\n data: descriptor,\n };\n const editId = crypto.randomUUID();\n if (pendingEditRef.current) {\n clearTimeout(pendingEditRef.current.timeout);\n }\n // Symbolicate has no AbortSignal; a wedged sandbox can hang forever.\n // Self-clear so a hung attempt can't permanently block inline edit.\n const timeout = window.setTimeout(() => {\n clearPendingEdit(editId);\n }, 5000);\n pendingEditRef.current = { editId, element, timeout };\n void symbolicateSelectedElement(descriptor).then(\n (symbolicated) => {\n if (pendingEditRef.current?.editId !== editId) {\n return;\n }\n void requestInlineTextEdit({\n editId,\n element: symbolicated,\n }).then(\n ({ allowed }) => {\n resolveEditRequest({ editId, allowed });\n },\n () => {\n resolveEditRequest({ editId, allowed: false });\n },\n );\n },\n () => {\n clearPendingEdit(editId);\n },\n );\n });\n },\n [clearPendingEdit, interactionSessionRef, resolveEditRequest],\n );\n\n // eslint-disable-next-line replo/no-use-effect -- Tears down the inline editor when leaving selection mode.\n useEffect(() => {\n if (displayMode !== \"selection\") {\n revert();\n }\n }, [displayMode, revert]);\n\n return {\n beginEditing,\n editingElementRef,\n formatCommand,\n revert,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import type { SelectModeOptimisticUpdatePayload } from "../../_vendor/schemas/generated/selectMode";
2
+ type SessionOutcome = "persisted" | "failed";
3
+ export declare function useOptimisticOverrides(): {
4
+ applyOptimisticUpdate: ({ editId, payload, targetSelector, targetMode, }: {
5
+ editId: string;
6
+ payload: SelectModeOptimisticUpdatePayload;
7
+ targetSelector: string;
8
+ targetMode: "primary" | "group";
9
+ }) => void;
10
+ markCommitted: (editId: string, sessionId: string) => void;
11
+ reapplyOverrides: ({ skipElement }: {
12
+ skipElement: HTMLElement | null;
13
+ }) => void;
14
+ resolveSessionOverrides: (sessionId: string, outcome: SessionOutcome) => void;
15
+ revertOverride: (editId: string) => void;
16
+ };
17
+ export {};
@@ -0,0 +1,283 @@
1
+ import { useCallback } from "react";
2
+ import { buildSelector } from "../descriptor/elementDescriptor";
3
+ import { getSourceLocationSiblingSelectors } from "./useSiblingResolution";
4
+ const OVERRIDE_REGISTRATION_TIMEOUT_MS = 15e3;
5
+ const SESSION_OUTCOME_RETENTION_MS = 6e4;
6
+ const OVERRIDE_ATTRIBUTE = "data-replo-select-overrides";
7
+ const OVERRIDE_REGISTRY_KEY = "reploSelectOverrideRegistryV2";
8
+ const SESSION_OUTCOME_REGISTRY_KEY = "reploSelectOverrideSessionOutcomesV2";
9
+ function getOverrideList() {
10
+ const existing = Reflect.get(window, OVERRIDE_REGISTRY_KEY);
11
+ if (Array.isArray(existing)) {
12
+ return existing;
13
+ }
14
+ const overrides = [];
15
+ Reflect.set(window, OVERRIDE_REGISTRY_KEY, overrides);
16
+ return overrides;
17
+ }
18
+ function setOverrideList(overrides) {
19
+ Reflect.set(window, OVERRIDE_REGISTRY_KEY, overrides);
20
+ }
21
+ function getSessionOutcomes() {
22
+ const existing = Reflect.get(window, SESSION_OUTCOME_REGISTRY_KEY);
23
+ if (existing instanceof Map) {
24
+ return existing;
25
+ }
26
+ const outcomes = /* @__PURE__ */ new Map();
27
+ Reflect.set(window, SESSION_OUTCOME_REGISTRY_KEY, outcomes);
28
+ return outcomes;
29
+ }
30
+ function getElementsForTargetMode({
31
+ element,
32
+ targetMode
33
+ }) {
34
+ if (targetMode === "primary") {
35
+ return [element];
36
+ }
37
+ return [
38
+ element,
39
+ ...getSourceLocationSiblingSelectors({ element }).map((selector) => document.querySelector(selector)).filter((candidate) => {
40
+ return candidate instanceof HTMLElement;
41
+ })
42
+ ];
43
+ }
44
+ function isImageSourcePayload({
45
+ element,
46
+ payload
47
+ }) {
48
+ return payload.kind === "attribute" && payload.name.toLowerCase() === "src" && element instanceof HTMLImageElement;
49
+ }
50
+ function getOriginalState({
51
+ element,
52
+ payload
53
+ }) {
54
+ if (payload.kind === "attribute") {
55
+ const names = isImageSourcePayload({ element, payload }) ? [payload.name, "srcset"] : [payload.name];
56
+ return {
57
+ kind: "attributes",
58
+ values: names.map((name) => ({
59
+ name,
60
+ value: element.getAttribute(name)
61
+ }))
62
+ };
63
+ }
64
+ if (payload.kind === "content") {
65
+ return { kind: "content", innerHTML: element.innerHTML };
66
+ }
67
+ return { kind: "css" };
68
+ }
69
+ function getStyleElement(token) {
70
+ return document.querySelector(
71
+ `style[data-replo-select-override="${token}"]`
72
+ );
73
+ }
74
+ function addToken(element, token) {
75
+ const tokens = new Set(
76
+ (element.getAttribute(OVERRIDE_ATTRIBUTE) ?? "").split(/\s+/).filter(Boolean)
77
+ );
78
+ tokens.add(token);
79
+ element.setAttribute(OVERRIDE_ATTRIBUTE, [...tokens].join(" "));
80
+ }
81
+ function removeToken(element, token) {
82
+ const tokens = (element.getAttribute(OVERRIDE_ATTRIBUTE) ?? "").split(/\s+/).filter((candidate) => candidate && candidate !== token);
83
+ if (tokens.length === 0) {
84
+ element.removeAttribute(OVERRIDE_ATTRIBUTE);
85
+ return;
86
+ }
87
+ element.setAttribute(OVERRIDE_ATTRIBUTE, tokens.join(" "));
88
+ }
89
+ function applyPayload({
90
+ element,
91
+ payload,
92
+ token
93
+ }) {
94
+ if (payload.kind === "css") {
95
+ addToken(element, token);
96
+ return;
97
+ }
98
+ if (payload.kind === "attribute") {
99
+ if (isImageSourcePayload({ element, payload })) {
100
+ element.removeAttribute("srcset");
101
+ }
102
+ element.setAttribute(payload.name, payload.value);
103
+ return;
104
+ }
105
+ element.innerHTML = payload.innerHTML;
106
+ }
107
+ function restoreOriginalState({
108
+ element,
109
+ originalState
110
+ }) {
111
+ if (originalState.kind === "attributes") {
112
+ for (const { name, value } of originalState.values) {
113
+ if (value === null) {
114
+ element.removeAttribute(name);
115
+ } else {
116
+ element.setAttribute(name, value);
117
+ }
118
+ }
119
+ return;
120
+ }
121
+ if (originalState.kind === "content") {
122
+ element.innerHTML = originalState.innerHTML;
123
+ }
124
+ }
125
+ function installCssRule(override) {
126
+ if (override.payload.kind !== "css") {
127
+ return;
128
+ }
129
+ const style = document.createElement("style");
130
+ style.dataset.reploSelectOverride = override.token;
131
+ style.textContent = `[${OVERRIDE_ATTRIBUTE}~="${override.token}"] { ${override.payload.property}: ${override.payload.value ?? "revert"} !important; }`;
132
+ document.head.append(style);
133
+ }
134
+ function resolveTarget(target) {
135
+ if (target.element.isConnected) {
136
+ return target.element;
137
+ }
138
+ const replacement = document.querySelector(target.selector);
139
+ return replacement instanceof HTMLElement ? replacement : null;
140
+ }
141
+ function reapplyOverride({
142
+ override,
143
+ skipElement
144
+ }) {
145
+ for (const target of override.targets) {
146
+ const element = resolveTarget(target);
147
+ if (!element || skipElement && element.contains(skipElement)) {
148
+ continue;
149
+ }
150
+ if (element !== target.element) {
151
+ target.element = element;
152
+ target.originalState = getOriginalState({
153
+ element,
154
+ payload: override.payload
155
+ });
156
+ }
157
+ applyPayload({ element, payload: override.payload, token: override.token });
158
+ }
159
+ }
160
+ function removeOverride({
161
+ editId,
162
+ outcome
163
+ }) {
164
+ const override = getOverrideList().find(
165
+ (candidate) => candidate.editId === editId
166
+ );
167
+ if (!override) {
168
+ return;
169
+ }
170
+ clearTimeout(override.registrationTimeout);
171
+ getStyleElement(override.token)?.remove();
172
+ for (const target of override.targets) {
173
+ const element = resolveTarget(target);
174
+ if (!element) {
175
+ continue;
176
+ }
177
+ removeToken(element, override.token);
178
+ if (outcome === "failed") {
179
+ restoreOriginalState({
180
+ element,
181
+ originalState: target.originalState
182
+ });
183
+ }
184
+ }
185
+ setOverrideList(
186
+ getOverrideList().filter((candidate) => candidate.editId !== editId)
187
+ );
188
+ if (outcome === "failed") {
189
+ for (const remainingOverride of getOverrideList()) {
190
+ reapplyOverride({ override: remainingOverride, skipElement: null });
191
+ }
192
+ }
193
+ }
194
+ function useOptimisticOverrides() {
195
+ const applyOptimisticUpdate = useCallback(
196
+ ({
197
+ editId,
198
+ payload,
199
+ targetSelector,
200
+ targetMode
201
+ }) => {
202
+ const targetElement = document.querySelector(targetSelector);
203
+ if (!(targetElement instanceof HTMLElement)) {
204
+ return;
205
+ }
206
+ removeOverride({ editId, outcome: "failed" });
207
+ const elements = getElementsForTargetMode({
208
+ element: targetElement,
209
+ targetMode
210
+ });
211
+ const token = `override-${editId}`;
212
+ const override = {
213
+ editId,
214
+ payload,
215
+ registrationTimeout: window.setTimeout(() => {
216
+ removeOverride({ editId, outcome: "failed" });
217
+ }, OVERRIDE_REGISTRATION_TIMEOUT_MS),
218
+ sessionId: null,
219
+ state: "live",
220
+ targets: elements.map((element) => ({
221
+ element,
222
+ originalState: getOriginalState({ element, payload }),
223
+ selector: buildSelector(element)
224
+ })),
225
+ token
226
+ };
227
+ getOverrideList().push(override);
228
+ installCssRule(override);
229
+ reapplyOverride({ override, skipElement: null });
230
+ },
231
+ []
232
+ );
233
+ const reapplyOverrides = useCallback(
234
+ ({ skipElement }) => {
235
+ for (const override of getOverrideList()) {
236
+ reapplyOverride({ override, skipElement });
237
+ }
238
+ },
239
+ []
240
+ );
241
+ const markCommitted = useCallback((editId, sessionId) => {
242
+ const override = getOverrideList().find((candidate) => {
243
+ return candidate.editId === editId;
244
+ });
245
+ if (!override) {
246
+ return;
247
+ }
248
+ clearTimeout(override.registrationTimeout);
249
+ override.sessionId = sessionId;
250
+ override.state = "fired";
251
+ const outcome = getSessionOutcomes().get(sessionId);
252
+ if (outcome) {
253
+ removeOverride({ editId, outcome });
254
+ }
255
+ }, []);
256
+ const resolveSessionOverrides = useCallback(
257
+ (sessionId, outcome) => {
258
+ getSessionOutcomes().set(sessionId, outcome);
259
+ window.setTimeout(() => {
260
+ getSessionOutcomes().delete(sessionId);
261
+ }, SESSION_OUTCOME_RETENTION_MS);
262
+ const editIds = getOverrideList().filter((override) => override.sessionId === sessionId).map((override) => override.editId);
263
+ for (const editId of editIds) {
264
+ removeOverride({ editId, outcome });
265
+ }
266
+ },
267
+ []
268
+ );
269
+ const revertOverride = useCallback((editId) => {
270
+ removeOverride({ editId, outcome: "failed" });
271
+ }, []);
272
+ return {
273
+ applyOptimisticUpdate,
274
+ markCommitted,
275
+ reapplyOverrides,
276
+ resolveSessionOverrides,
277
+ revertOverride
278
+ };
279
+ }
280
+ export {
281
+ useOptimisticOverrides
282
+ };
283
+ //# sourceMappingURL=useOptimisticOverrides.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../select-mode/hooks/useOptimisticOverrides.ts"],
4
+ "sourcesContent": ["import type { SelectModeOptimisticUpdatePayload } from \"schemas/generated/selectMode\";\n\nimport { useCallback } from \"react\";\n\nimport { buildSelector } from \"../descriptor/elementDescriptor\";\nimport { getSourceLocationSiblingSelectors } from \"./useSiblingResolution\";\n\ntype OverrideTarget = {\n element: HTMLElement;\n originalState: OriginalPayloadState;\n selector: string;\n};\n\ntype OriginalPayloadState =\n | {\n kind: \"attributes\";\n values: { name: string; value: string | null }[];\n }\n | { kind: \"content\"; innerHTML: string }\n | { kind: \"css\" };\n\ntype IframeOverride = {\n editId: string;\n payload: SelectModeOptimisticUpdatePayload;\n registrationTimeout: number;\n sessionId: string | null;\n state: \"live\" | \"fired\";\n targets: OverrideTarget[];\n token: string;\n};\n\nconst OVERRIDE_REGISTRATION_TIMEOUT_MS = 15_000;\nconst SESSION_OUTCOME_RETENTION_MS = 60_000;\nconst OVERRIDE_ATTRIBUTE = \"data-replo-select-overrides\";\nconst OVERRIDE_REGISTRY_KEY = \"reploSelectOverrideRegistryV2\";\nconst SESSION_OUTCOME_REGISTRY_KEY = \"reploSelectOverrideSessionOutcomesV2\";\n\ntype SessionOutcome = \"persisted\" | \"failed\";\n\nfunction getOverrideList(): IframeOverride[] {\n const existing: unknown = Reflect.get(window, OVERRIDE_REGISTRY_KEY);\n if (Array.isArray(existing)) {\n return existing;\n }\n const overrides: IframeOverride[] = [];\n Reflect.set(window, OVERRIDE_REGISTRY_KEY, overrides);\n return overrides;\n}\n\nfunction setOverrideList(overrides: IframeOverride[]) {\n Reflect.set(window, OVERRIDE_REGISTRY_KEY, overrides);\n}\n\nfunction getSessionOutcomes(): Map<string, SessionOutcome> {\n const existing: unknown = Reflect.get(window, SESSION_OUTCOME_REGISTRY_KEY);\n if (existing instanceof Map) {\n return existing;\n }\n const outcomes = new Map<string, SessionOutcome>();\n Reflect.set(window, SESSION_OUTCOME_REGISTRY_KEY, outcomes);\n return outcomes;\n}\n\nfunction getElementsForTargetMode({\n element,\n targetMode,\n}: {\n element: HTMLElement;\n targetMode: \"primary\" | \"group\";\n}) {\n if (targetMode === \"primary\") {\n return [element];\n }\n\n return [\n element,\n ...getSourceLocationSiblingSelectors({ element })\n .map((selector) => document.querySelector(selector))\n .filter((candidate): candidate is HTMLElement => {\n return candidate instanceof HTMLElement;\n }),\n ];\n}\n\nfunction isImageSourcePayload({\n element,\n payload,\n}: {\n element: HTMLElement;\n payload: SelectModeOptimisticUpdatePayload;\n}) {\n return (\n payload.kind === \"attribute\" &&\n payload.name.toLowerCase() === \"src\" &&\n element instanceof HTMLImageElement\n );\n}\n\nfunction getOriginalState({\n element,\n payload,\n}: {\n element: HTMLElement;\n payload: SelectModeOptimisticUpdatePayload;\n}): OriginalPayloadState {\n if (payload.kind === \"attribute\") {\n const names = isImageSourcePayload({ element, payload })\n ? [payload.name, \"srcset\"]\n : [payload.name];\n return {\n kind: \"attributes\",\n values: names.map((name) => ({\n name,\n value: element.getAttribute(name),\n })),\n };\n }\n if (payload.kind === \"content\") {\n return { kind: \"content\", innerHTML: element.innerHTML };\n }\n return { kind: \"css\" };\n}\n\nfunction getStyleElement(token: string) {\n return document.querySelector<HTMLStyleElement>(\n `style[data-replo-select-override=\"${token}\"]`,\n );\n}\n\nfunction addToken(element: HTMLElement, token: string) {\n const tokens = new Set(\n (element.getAttribute(OVERRIDE_ATTRIBUTE) ?? \"\")\n .split(/\\s+/)\n .filter(Boolean),\n );\n tokens.add(token);\n element.setAttribute(OVERRIDE_ATTRIBUTE, [...tokens].join(\" \"));\n}\n\nfunction removeToken(element: HTMLElement, token: string) {\n const tokens = (element.getAttribute(OVERRIDE_ATTRIBUTE) ?? \"\")\n .split(/\\s+/)\n .filter((candidate) => candidate && candidate !== token);\n if (tokens.length === 0) {\n element.removeAttribute(OVERRIDE_ATTRIBUTE);\n return;\n }\n element.setAttribute(OVERRIDE_ATTRIBUTE, tokens.join(\" \"));\n}\n\nfunction applyPayload({\n element,\n payload,\n token,\n}: {\n element: HTMLElement;\n payload: SelectModeOptimisticUpdatePayload;\n token: string;\n}) {\n if (payload.kind === \"css\") {\n addToken(element, token);\n return;\n }\n if (payload.kind === \"attribute\") {\n if (isImageSourcePayload({ element, payload })) {\n element.removeAttribute(\"srcset\");\n }\n element.setAttribute(payload.name, payload.value);\n return;\n }\n element.innerHTML = payload.innerHTML;\n}\n\nfunction restoreOriginalState({\n element,\n originalState,\n}: {\n element: HTMLElement;\n originalState: OriginalPayloadState;\n}) {\n if (originalState.kind === \"attributes\") {\n for (const { name, value } of originalState.values) {\n if (value === null) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value);\n }\n }\n return;\n }\n if (originalState.kind === \"content\") {\n element.innerHTML = originalState.innerHTML;\n }\n}\n\nfunction installCssRule(override: IframeOverride) {\n if (override.payload.kind !== \"css\") {\n return;\n }\n const style = document.createElement(\"style\");\n style.dataset.reploSelectOverride = override.token;\n style.textContent = `[${OVERRIDE_ATTRIBUTE}~=\"${override.token}\"] { ${override.payload.property}: ${override.payload.value ?? \"revert\"} !important; }`;\n document.head.append(style);\n}\n\nfunction resolveTarget(target: OverrideTarget) {\n if (target.element.isConnected) {\n return target.element;\n }\n const replacement = document.querySelector(target.selector);\n return replacement instanceof HTMLElement ? replacement : null;\n}\n\nfunction reapplyOverride({\n override,\n skipElement,\n}: {\n override: IframeOverride;\n skipElement: HTMLElement | null;\n}) {\n for (const target of override.targets) {\n const element = resolveTarget(target);\n if (!element || (skipElement && element.contains(skipElement))) {\n continue;\n }\n if (element !== target.element) {\n target.element = element;\n target.originalState = getOriginalState({\n element,\n payload: override.payload,\n });\n }\n applyPayload({ element, payload: override.payload, token: override.token });\n }\n}\n\nfunction removeOverride({\n editId,\n outcome,\n}: {\n editId: string;\n outcome: SessionOutcome;\n}) {\n const override = getOverrideList().find(\n (candidate) => candidate.editId === editId,\n );\n if (!override) {\n return;\n }\n\n clearTimeout(override.registrationTimeout);\n getStyleElement(override.token)?.remove();\n for (const target of override.targets) {\n const element = resolveTarget(target);\n if (!element) {\n continue;\n }\n removeToken(element, override.token);\n if (outcome === \"failed\") {\n restoreOriginalState({\n element,\n originalState: target.originalState,\n });\n }\n }\n setOverrideList(\n getOverrideList().filter((candidate) => candidate.editId !== editId),\n );\n\n if (outcome === \"failed\") {\n for (const remainingOverride of getOverrideList()) {\n reapplyOverride({ override: remainingOverride, skipElement: null });\n }\n }\n}\n\nexport function useOptimisticOverrides() {\n const applyOptimisticUpdate = useCallback(\n ({\n editId,\n payload,\n targetSelector,\n targetMode,\n }: {\n editId: string;\n payload: SelectModeOptimisticUpdatePayload;\n targetSelector: string;\n targetMode: \"primary\" | \"group\";\n }) => {\n const targetElement = document.querySelector(targetSelector);\n if (!(targetElement instanceof HTMLElement)) {\n return;\n }\n\n removeOverride({ editId, outcome: \"failed\" });\n const elements = getElementsForTargetMode({\n element: targetElement,\n targetMode,\n });\n const token = `override-${editId}`;\n const override: IframeOverride = {\n editId,\n payload,\n registrationTimeout: window.setTimeout(() => {\n removeOverride({ editId, outcome: \"failed\" });\n }, OVERRIDE_REGISTRATION_TIMEOUT_MS),\n sessionId: null,\n state: \"live\",\n targets: elements.map((element) => ({\n element,\n originalState: getOriginalState({ element, payload }),\n selector: buildSelector(element),\n })),\n token,\n };\n getOverrideList().push(override);\n installCssRule(override);\n reapplyOverride({ override, skipElement: null });\n },\n [],\n );\n\n const reapplyOverrides = useCallback(\n ({ skipElement }: { skipElement: HTMLElement | null }) => {\n for (const override of getOverrideList()) {\n reapplyOverride({ override, skipElement });\n }\n },\n [],\n );\n\n const markCommitted = useCallback((editId: string, sessionId: string) => {\n const override = getOverrideList().find((candidate) => {\n return candidate.editId === editId;\n });\n if (!override) {\n return;\n }\n clearTimeout(override.registrationTimeout);\n override.sessionId = sessionId;\n override.state = \"fired\";\n const outcome = getSessionOutcomes().get(sessionId);\n if (outcome) {\n removeOverride({ editId, outcome });\n }\n }, []);\n\n const resolveSessionOverrides = useCallback(\n (sessionId: string, outcome: SessionOutcome) => {\n getSessionOutcomes().set(sessionId, outcome);\n window.setTimeout(() => {\n getSessionOutcomes().delete(sessionId);\n }, SESSION_OUTCOME_RETENTION_MS);\n const editIds = getOverrideList()\n .filter((override) => override.sessionId === sessionId)\n .map((override) => override.editId);\n for (const editId of editIds) {\n removeOverride({ editId, outcome });\n }\n },\n [],\n );\n\n const revertOverride = useCallback((editId: string) => {\n removeOverride({ editId, outcome: \"failed\" });\n }, []);\n\n return {\n applyOptimisticUpdate,\n markCommitted,\n reapplyOverrides,\n resolveSessionOverrides,\n revertOverride,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,36 @@
1
+ import type { DisplayMode, SelectModeFormatCommand, SelectModeOptimisticUpdatePayload, SelectModeSelectionType } from "../../_vendor/schemas/generated/selectMode";
2
+ /**
3
+ * Inbound side of the iframe<->parent selectMode bridge.
4
+ *
5
+ * Listens for Website Builder → canopy methods and applies them locally:
6
+ * - `setDisplayMode`: switch the iframe between `browse` and `selection`.
7
+ * - `setSelectionType`: declarative push of the parent's current
8
+ * selection type. The interesting transition is `* → "none"`, which
9
+ * tears down any in-iframe selection frame / in-progress drag — agent-ui
10
+ * drives it from `agentUiState.selectionType` so panel dismiss / submit
11
+ * flow through the same path as exiting selection mode entirely. The
12
+ * regression test in `EditorSelection.spec.tsx` covers the case where
13
+ * losing this used to lock the iframe out of further selections.
14
+ *
15
+ * Also fires `iframeReady` on mount so the parent knows it's safe to push
16
+ * the initial `setDisplayMode` — without this handshake the parent would
17
+ * race the iframe's mount and the first message could be lost.
18
+ *
19
+ * @author Kevin O'Connell 2026-04-17
20
+ */
21
+ export declare function useParentMessaging({ applyOptimisticUpdate, markCommitted, resolveSessionOverrides, onEnterEditMode, onExitEditMode, onFormatCommand, revertOverride, setDisplayMode, setSelectionType, }: {
22
+ applyOptimisticUpdate?: (input: {
23
+ editId: string;
24
+ payload: SelectModeOptimisticUpdatePayload;
25
+ targetSelector: string;
26
+ targetMode: "primary" | "group";
27
+ }) => void;
28
+ markCommitted?: (editId: string, sessionId: string) => void;
29
+ resolveSessionOverrides?: (sessionId: string, outcome: "persisted" | "failed") => void;
30
+ onEnterEditMode?: () => void;
31
+ onExitEditMode?: () => void;
32
+ onFormatCommand?: (command: SelectModeFormatCommand) => void;
33
+ revertOverride?: (editId: string) => void;
34
+ setDisplayMode: (displayMode: DisplayMode) => void;
35
+ setSelectionType: (selectionType: SelectModeSelectionType) => void;
36
+ }): void;