@ui-annotate/react-vite 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 (100) hide show
  1. package/dist/code-open.d.ts +17 -0
  2. package/dist/code-open.js +82 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +4 -0
  5. package/dist/inspector-transform.d.ts +59 -0
  6. package/dist/inspector-transform.js +218 -0
  7. package/dist/protocol/constants.d.ts +7 -0
  8. package/dist/protocol/constants.js +31 -0
  9. package/dist/protocol/ids.d.ts +3 -0
  10. package/dist/protocol/ids.js +15 -0
  11. package/dist/protocol/index.d.ts +4 -0
  12. package/dist/protocol/index.js +4 -0
  13. package/dist/protocol/paths.d.ts +3 -0
  14. package/dist/protocol/paths.js +36 -0
  15. package/dist/protocol/task-model.d.ts +35 -0
  16. package/dist/protocol/task-model.js +68 -0
  17. package/dist/runtime/app/AnnotateToolbar.d.ts +9 -0
  18. package/dist/runtime/app/AnnotateToolbar.js +14 -0
  19. package/dist/runtime/app/AnnotateWindows.d.ts +28 -0
  20. package/dist/runtime/app/AnnotateWindows.js +8 -0
  21. package/dist/runtime/app/UiAnnotate.d.ts +5 -0
  22. package/dist/runtime/app/UiAnnotate.js +540 -0
  23. package/dist/runtime/index.d.ts +5 -0
  24. package/dist/runtime/index.js +4 -0
  25. package/dist/runtime/inspector/target-inspection.d.ts +10 -0
  26. package/dist/runtime/inspector/target-inspection.js +337 -0
  27. package/dist/runtime/layout/annotate-storage.d.ts +9 -0
  28. package/dist/runtime/layout/annotate-storage.js +134 -0
  29. package/dist/runtime/layout/use-annotate-layout.d.ts +17 -0
  30. package/dist/runtime/layout/use-annotate-layout.js +147 -0
  31. package/dist/runtime/overlay/SelectionOverlay.d.ts +7 -0
  32. package/dist/runtime/overlay/SelectionOverlay.js +95 -0
  33. package/dist/runtime/shared/annotate-constants.d.ts +13 -0
  34. package/dist/runtime/shared/annotate-constants.js +13 -0
  35. package/dist/runtime/shared/annotate-types.d.ts +36 -0
  36. package/dist/runtime/shared/annotate-types.js +1 -0
  37. package/dist/runtime/shared/clipboard.d.ts +1 -0
  38. package/dist/runtime/shared/clipboard.js +33 -0
  39. package/dist/runtime/style.css +206 -0
  40. package/dist/runtime/task/annotate-task.d.ts +16 -0
  41. package/dist/runtime/task/annotate-task.js +85 -0
  42. package/dist/runtime/task/use-annotate-task.d.ts +16 -0
  43. package/dist/runtime/task/use-annotate-task.js +115 -0
  44. package/dist/runtime/windows/AnnotateSettingsWindow.d.ts +6 -0
  45. package/dist/runtime/windows/AnnotateSettingsWindow.js +5 -0
  46. package/dist/runtime/windows/AnnotateWindow.d.ts +21 -0
  47. package/dist/runtime/windows/AnnotateWindow.js +83 -0
  48. package/dist/runtime/windows/AnnotateWindowFrame.d.ts +26 -0
  49. package/dist/runtime/windows/AnnotateWindowFrame.js +56 -0
  50. package/dist/runtime/windows/TargetTraceTree.d.ts +12 -0
  51. package/dist/runtime/windows/TargetTraceTree.js +163 -0
  52. package/dist/runtime/windows/window-shared.d.ts +14 -0
  53. package/dist/runtime/windows/window-shared.js +41 -0
  54. package/dist/task-api.d.ts +15 -0
  55. package/dist/task-api.js +239 -0
  56. package/dist/ui/components/accordion.d.ts +7 -0
  57. package/dist/ui/components/accordion.js +18 -0
  58. package/dist/ui/components/alert-dialog.d.ts +18 -0
  59. package/dist/ui/components/alert-dialog.js +41 -0
  60. package/dist/ui/components/alert.d.ts +9 -0
  61. package/dist/ui/components/alert.js +24 -0
  62. package/dist/ui/components/badge.d.ts +9 -0
  63. package/dist/ui/components/badge.js +24 -0
  64. package/dist/ui/components/breadcrumb.d.ts +11 -0
  65. package/dist/ui/components/breadcrumb.js +27 -0
  66. package/dist/ui/components/button.d.ts +10 -0
  67. package/dist/ui/components/button.js +31 -0
  68. package/dist/ui/components/card.d.ts +9 -0
  69. package/dist/ui/components/card.js +24 -0
  70. package/dist/ui/components/dropdown-menu.d.ts +11 -0
  71. package/dist/ui/components/dropdown-menu.js +21 -0
  72. package/dist/ui/components/input.d.ts +3 -0
  73. package/dist/ui/components/input.js +6 -0
  74. package/dist/ui/components/scroll-area.d.ts +5 -0
  75. package/dist/ui/components/scroll-area.js +12 -0
  76. package/dist/ui/components/separator.d.ts +4 -0
  77. package/dist/ui/components/separator.js +8 -0
  78. package/dist/ui/components/switch.d.ts +6 -0
  79. package/dist/ui/components/switch.js +7 -0
  80. package/dist/ui/components/table.d.ts +10 -0
  81. package/dist/ui/components/table.js +27 -0
  82. package/dist/ui/components/tabs.d.ts +11 -0
  83. package/dist/ui/components/tabs.js +28 -0
  84. package/dist/ui/components/textarea.d.ts +3 -0
  85. package/dist/ui/components/textarea.js +6 -0
  86. package/dist/ui/components/toggle-group.d.ts +9 -0
  87. package/dist/ui/components/toggle-group.js +22 -0
  88. package/dist/ui/components/toggle.d.ts +9 -0
  89. package/dist/ui/components/toggle.js +25 -0
  90. package/dist/ui/components/tooltip.d.ts +7 -0
  91. package/dist/ui/components/tooltip.js +18 -0
  92. package/dist/ui/index.d.ts +2 -0
  93. package/dist/ui/index.js +2 -0
  94. package/dist/ui/lib/utils.d.ts +2 -0
  95. package/dist/ui/lib/utils.js +5 -0
  96. package/dist/ui/portal/portal-container.d.ts +13 -0
  97. package/dist/ui/portal/portal-container.js +12 -0
  98. package/dist/ui-annotate-plugin.d.ts +28 -0
  99. package/dist/ui-annotate-plugin.js +227 -0
  100. package/package.json +55 -0
@@ -0,0 +1,28 @@
1
+ import type { PointerEvent as ReactPointerEvent, ReactNode } from "react";
2
+ import type { AnnotateTarget, TraceNodeRef } from "../../protocol/task-model.js";
3
+ import { type AnnotateWindowResizeDirection } from "../windows/AnnotateWindowFrame.js";
4
+ import type { AnnotateSettings, AnnotateWindowId, AnnotateWindowMap } from "../shared/annotate-types.js";
5
+ import type { SaveStatus, TaskStatus } from "../windows/window-shared.js";
6
+ export declare function AnnotateWindows({ activeTargetId, saveError, saveStatus, settings, targets, taskError, taskStatus, windows, onActiveTargetChange, onAddComment, onBeginMove, onBeginResize, onChangeComment, onClose, onDeleteComment, onDeleteTarget, onGetAnnotateTraceNodeElement, onHoverAnnotateTraceNode, onOpenCodePath, onResetTargets, onShowNativeTraceNodesChange }: {
7
+ activeTargetId: string | null;
8
+ saveError: string | null;
9
+ saveStatus: SaveStatus;
10
+ settings: AnnotateSettings;
11
+ targets: AnnotateTarget[];
12
+ taskError: string | null;
13
+ taskStatus: TaskStatus;
14
+ windows: AnnotateWindowMap;
15
+ onActiveTargetChange(targetId: string): void;
16
+ onAddComment(targetId: string): void;
17
+ onBeginMove(id: AnnotateWindowId, event: ReactPointerEvent<HTMLElement>): void;
18
+ onBeginResize(id: AnnotateWindowId, direction: AnnotateWindowResizeDirection, event: ReactPointerEvent<HTMLDivElement>): void;
19
+ onChangeComment(targetId: string, index: number, value: string): void;
20
+ onClose(id: AnnotateWindowId): void;
21
+ onDeleteComment(targetId: string, index: number): void;
22
+ onDeleteTarget(targetId: string): void;
23
+ onGetAnnotateTraceNodeElement(targetId: string, node: TraceNodeRef): Element | null;
24
+ onHoverAnnotateTraceNode(targetId: string, node: TraceNodeRef | null): void;
25
+ onOpenCodePath(path: string): void;
26
+ onResetTargets(): void;
27
+ onShowNativeTraceNodesChange(value: boolean): void;
28
+ }): ReactNode;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { BugIcon, SettingsIcon } from "lucide-react";
3
+ import { AnnotateWindow } from "../windows/AnnotateWindow.js";
4
+ import { AnnotateSettingsWindow } from "../windows/AnnotateSettingsWindow.js";
5
+ import { AnnotateWindowFrame } from "../windows/AnnotateWindowFrame.js";
6
+ export function AnnotateWindows({ activeTargetId, saveError, saveStatus, settings, targets, taskError, taskStatus, windows, onActiveTargetChange, onAddComment, onBeginMove, onBeginResize, onChangeComment, onClose, onDeleteComment, onDeleteTarget, onGetAnnotateTraceNodeElement, onHoverAnnotateTraceNode, onOpenCodePath, onResetTargets, onShowNativeTraceNodesChange }) {
7
+ return (_jsxs(_Fragment, { children: [windows.annotate.open ? (_jsx(AnnotateWindowFrame, { id: "annotate", label: "Annotate component", icon: BugIcon, title: "Annotate component", state: windows.annotate, onBeginMove: onBeginMove, onBeginResize: onBeginResize, onClose: onClose, children: _jsx(AnnotateWindow, { activeTargetId: activeTargetId, saveError: saveError, saveStatus: saveStatus, targets: targets, taskError: taskError, taskStatus: taskStatus, getTraceNodeElement: (targetId) => (node) => onGetAnnotateTraceNodeElement(targetId, node), onActiveTargetChange: onActiveTargetChange, onAddComment: onAddComment, onChangeComment: onChangeComment, onDeleteComment: onDeleteComment, onDeleteTarget: onDeleteTarget, onHoverTraceNode: onHoverAnnotateTraceNode, onOpenPath: onOpenCodePath, onResetTargets: onResetTargets }) })) : null, windows.settings.open ? (_jsx(AnnotateWindowFrame, { id: "settings", label: "Settings", icon: SettingsIcon, title: "Settings", state: windows.settings, onBeginMove: onBeginMove, onBeginResize: onBeginResize, onClose: onClose, children: _jsx(AnnotateSettingsWindow, { settings: settings, onShowNativeTraceNodesChange: onShowNativeTraceNodesChange }) })) : null] }));
8
+ }
@@ -0,0 +1,5 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function UiAnnotate({ portalContainer, rootElement }: {
3
+ portalContainer?: HTMLElement | null;
4
+ rootElement?: HTMLElement | null;
5
+ }): ReactNode;
@@ -0,0 +1,540 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { isSameTargetTrace } from "../../protocol/task-model.js";
4
+ import { PortalContainerProvider } from "../../ui/portal/portal-container.js";
5
+ import { TooltipProvider } from "../../ui/components/tooltip.js";
6
+ import { findElementForTarget, findElementForTraceNode, findSelectableTarget, isEditableEventTarget } from "../inspector/target-inspection.js";
7
+ import { useAnnotateLayout } from "../layout/use-annotate-layout.js";
8
+ import { SelectionOverlay } from "../overlay/SelectionOverlay.js";
9
+ import { ANNOTATE_PORTAL_Z_INDEX, ANNOTATE_ROOT_ID } from "../shared/annotate-constants.js";
10
+ import { deleteAllTargets, deleteTarget, registerAnnotateTarget, updateTargetComments } from "../task/annotate-task.js";
11
+ import { useAnnotateTask } from "../task/use-annotate-task.js";
12
+ import { AnnotateToolbar } from "./AnnotateToolbar.js";
13
+ import { AnnotateWindows } from "./AnnotateWindows.js";
14
+ const HOVER_MATCH_INTERVAL_MS = 80;
15
+ const TARGET_MATCH_DELAY_MS = 160;
16
+ export function UiAnnotate({ portalContainer, rootElement }) {
17
+ const annotateRootRef = useRef(rootElement ?? null);
18
+ const [hoverTarget, setHoverTarget] = useState(null);
19
+ const [frozenHoverTarget, setFrozenHoverTarget] = useState(null);
20
+ const [codeOpenMode, setCodeOpenMode] = useState(false);
21
+ const [annotateMode, setAnnotateMode] = useState(false);
22
+ const [activeTargetId, setActiveTargetId] = useState(null);
23
+ const [traceNodeHover, setTraceNodeHover] = useState(null);
24
+ const [targetElementMatches, setTargetElementMatches] = useState([]);
25
+ const hoverTargetRef = useRef(null);
26
+ const frozenHoverTargetRef = useRef(null);
27
+ const annotateModeRef = useRef(false);
28
+ const codeOpenModeRef = useRef(false);
29
+ const codeOpenPointerUpHandledRef = useRef(false);
30
+ const hoverMatchFrameRef = useRef(null);
31
+ const lastHoverMatchTimeRef = useRef(0);
32
+ const lastHoverElementRef = useRef(null);
33
+ const { commitTask, flushTaskSave, saveError, saveStatus, task, taskError, taskRef, taskStatus, taskStatusRef } = useAnnotateTask();
34
+ const { beginToolbarDrag, beginWindowMove, beginWindowResize, closeWindow, openWindow, setSettings, settings, toolbarPosition, windows } = useAnnotateLayout();
35
+ const activeHoverTarget = frozenHoverTarget ?? hoverTarget;
36
+ const activeAnnotateTarget = task.targets.find((target) => target.id === activeTargetId) ?? task.targets[0] ?? null;
37
+ const showCodeOpenCursor = annotateMode && codeOpenMode;
38
+ const targetMatchKey = useMemo(() => createTargetMatchKey(task.targets), [task.targets]);
39
+ useEffect(() => {
40
+ annotateRootRef.current = rootElement ?? document.getElementById(ANNOTATE_ROOT_ID);
41
+ }, [rootElement]);
42
+ useEffect(() => {
43
+ hoverTargetRef.current = hoverTarget;
44
+ }, [hoverTarget]);
45
+ useEffect(() => {
46
+ frozenHoverTargetRef.current = frozenHoverTarget;
47
+ }, [frozenHoverTarget]);
48
+ useEffect(() => {
49
+ annotateModeRef.current = annotateMode;
50
+ }, [annotateMode]);
51
+ useEffect(() => {
52
+ codeOpenModeRef.current = codeOpenMode;
53
+ }, [codeOpenMode]);
54
+ useEffect(() => {
55
+ const styleElement = ensureCodeOpenCursorStyle();
56
+ styleElement.disabled = !showCodeOpenCursor;
57
+ return () => {
58
+ styleElement.disabled = true;
59
+ };
60
+ }, [showCodeOpenCursor]);
61
+ useEffect(() => {
62
+ if (!import.meta.hot) {
63
+ return;
64
+ }
65
+ const resetTraceHover = () => {
66
+ setTraceNodeHover(null);
67
+ };
68
+ import.meta.hot.on("vite:afterUpdate", resetTraceHover);
69
+ return () => {
70
+ import.meta.hot?.off("vite:afterUpdate", resetTraceHover);
71
+ };
72
+ }, []);
73
+ useEffect(() => {
74
+ const previewRoot = document.body;
75
+ function handlePointerMove(event) {
76
+ if (!annotateModeRef.current || isInsideAnnotateRoot(event.target)) {
77
+ return;
78
+ }
79
+ if (!(event.target instanceof Element)) {
80
+ scheduleHoverMatch(null, false);
81
+ return;
82
+ }
83
+ scheduleHoverMatch(event.target, event.ctrlKey || event.metaKey);
84
+ }
85
+ function runHoverMatch(target, modifierPressed) {
86
+ if (!annotateModeRef.current) {
87
+ clearHoverState();
88
+ return;
89
+ }
90
+ if (modifierPressed) {
91
+ let targetToFreeze = frozenHoverTargetRef.current ?? hoverTargetRef.current;
92
+ if (!targetToFreeze && target) {
93
+ const selectableTarget = findSelectableTarget(target, previewRoot, null, settings.showNativeTraceNodes);
94
+ if (selectableTarget && !isInsideAnnotateRoot(selectableTarget.element)) {
95
+ targetToFreeze = selectableTarget;
96
+ hoverTargetRef.current = selectableTarget;
97
+ setHoverTarget(selectableTarget);
98
+ }
99
+ }
100
+ if (targetToFreeze && !frozenHoverTargetRef.current) {
101
+ frozenHoverTargetRef.current = targetToFreeze;
102
+ setFrozenHoverTarget(targetToFreeze);
103
+ }
104
+ codeOpenModeRef.current = targetToFreeze !== null;
105
+ setCodeOpenMode(codeOpenModeRef.current);
106
+ return;
107
+ }
108
+ if (!modifierPressed && frozenHoverTargetRef.current) {
109
+ frozenHoverTargetRef.current = null;
110
+ setFrozenHoverTarget(null);
111
+ }
112
+ if (!target) {
113
+ clearHoverState();
114
+ return;
115
+ }
116
+ const selectableTarget = findSelectableTarget(target, previewRoot, null, settings.showNativeTraceNodes);
117
+ if (!selectableTarget || isInsideAnnotateRoot(selectableTarget.element)) {
118
+ clearHoverState();
119
+ return;
120
+ }
121
+ hoverTargetRef.current = selectableTarget;
122
+ setHoverTarget(selectableTarget);
123
+ codeOpenModeRef.current = modifierPressed;
124
+ setCodeOpenMode(codeOpenModeRef.current);
125
+ }
126
+ function scheduleHoverMatch(target, modifierPressed) {
127
+ const now = window.performance.now();
128
+ const isRepeatTarget = target !== null && target === lastHoverElementRef.current;
129
+ if (isRepeatTarget && now - lastHoverMatchTimeRef.current < HOVER_MATCH_INTERVAL_MS) {
130
+ return;
131
+ }
132
+ lastHoverElementRef.current = target;
133
+ if (hoverMatchFrameRef.current !== null) {
134
+ window.cancelAnimationFrame(hoverMatchFrameRef.current);
135
+ }
136
+ hoverMatchFrameRef.current = window.requestAnimationFrame(() => {
137
+ hoverMatchFrameRef.current = null;
138
+ lastHoverMatchTimeRef.current = window.performance.now();
139
+ runHoverMatch(target, modifierPressed);
140
+ });
141
+ }
142
+ function handlePointerLeave() {
143
+ if (codeOpenModeRef.current && frozenHoverTargetRef.current) {
144
+ return;
145
+ }
146
+ clearHoverState();
147
+ }
148
+ previewRoot.addEventListener("pointermove", handlePointerMove);
149
+ window.addEventListener("pointerleave", handlePointerLeave);
150
+ return () => {
151
+ if (hoverMatchFrameRef.current !== null) {
152
+ window.cancelAnimationFrame(hoverMatchFrameRef.current);
153
+ hoverMatchFrameRef.current = null;
154
+ }
155
+ previewRoot.removeEventListener("pointermove", handlePointerMove);
156
+ window.removeEventListener("pointerleave", handlePointerLeave);
157
+ };
158
+ }, [settings.showNativeTraceNodes]);
159
+ useEffect(() => {
160
+ if (!annotateMode) {
161
+ return;
162
+ }
163
+ let cancelled = false;
164
+ const timeoutId = window.setTimeout(() => {
165
+ const matches = [];
166
+ for (const target of taskRef.current.targets) {
167
+ const element = findElementForTarget(document.body, target, null, settings.showNativeTraceNodes);
168
+ if (!element || isInsideAnnotateRoot(element)) {
169
+ continue;
170
+ }
171
+ matches.push({
172
+ targetId: target.id,
173
+ element
174
+ });
175
+ }
176
+ if (!cancelled) {
177
+ setTargetElementMatches(matches);
178
+ }
179
+ }, TARGET_MATCH_DELAY_MS);
180
+ return () => {
181
+ cancelled = true;
182
+ window.clearTimeout(timeoutId);
183
+ };
184
+ }, [annotateMode, settings.showNativeTraceNodes, targetMatchKey]);
185
+ useEffect(() => {
186
+ const previewRoot = document.body;
187
+ const stopPreviewInteraction = (event) => {
188
+ if (!annotateModeRef.current || isInsideAnnotateRoot(event.target)) {
189
+ return;
190
+ }
191
+ event.preventDefault();
192
+ event.stopPropagation();
193
+ };
194
+ const handleClick = (event) => {
195
+ if (!annotateModeRef.current || isInsideAnnotateRoot(event.target)) {
196
+ return;
197
+ }
198
+ stopPreviewInteraction(event);
199
+ if (codeOpenPointerUpHandledRef.current) {
200
+ codeOpenPointerUpHandledRef.current = false;
201
+ return;
202
+ }
203
+ if (!(event.target instanceof Element)) {
204
+ return;
205
+ }
206
+ if (event.ctrlKey || event.metaKey) {
207
+ void handleOpenCodeFromElementAndExitAnnotate(event.target, "frozen-first");
208
+ return;
209
+ }
210
+ if (handleAnnotateTargetFromElement(event.target)) {
211
+ setAnnotateMode(false);
212
+ }
213
+ };
214
+ const handlePointerUp = (event) => {
215
+ if (!annotateModeRef.current ||
216
+ isInsideAnnotateRoot(event.target) ||
217
+ event.button !== 0 ||
218
+ (!event.ctrlKey && !event.metaKey)) {
219
+ return;
220
+ }
221
+ stopPreviewInteraction(event);
222
+ if (event.target instanceof Element) {
223
+ codeOpenPointerUpHandledRef.current = true;
224
+ void handleOpenCodeFromElementAndExitAnnotate(event.target, "frozen-first");
225
+ }
226
+ };
227
+ const handleFocusIn = (event) => {
228
+ if (!annotateModeRef.current || isInsideAnnotateRoot(event.target)) {
229
+ return;
230
+ }
231
+ stopPreviewInteraction(event);
232
+ if (event.target instanceof HTMLElement) {
233
+ event.target.blur();
234
+ }
235
+ };
236
+ previewRoot.addEventListener("pointerup", handlePointerUp, true);
237
+ previewRoot.addEventListener("click", handleClick, true);
238
+ previewRoot.addEventListener("dblclick", stopPreviewInteraction, true);
239
+ previewRoot.addEventListener("contextmenu", stopPreviewInteraction, true);
240
+ previewRoot.addEventListener("submit", stopPreviewInteraction, true);
241
+ previewRoot.addEventListener("dragstart", stopPreviewInteraction, true);
242
+ previewRoot.addEventListener("focusin", handleFocusIn, true);
243
+ return () => {
244
+ previewRoot.removeEventListener("pointerup", handlePointerUp, true);
245
+ previewRoot.removeEventListener("click", handleClick, true);
246
+ previewRoot.removeEventListener("dblclick", stopPreviewInteraction, true);
247
+ previewRoot.removeEventListener("contextmenu", stopPreviewInteraction, true);
248
+ previewRoot.removeEventListener("submit", stopPreviewInteraction, true);
249
+ previewRoot.removeEventListener("dragstart", stopPreviewInteraction, true);
250
+ previewRoot.removeEventListener("focusin", handleFocusIn, true);
251
+ };
252
+ }, [settings.showNativeTraceNodes]);
253
+ useEffect(() => {
254
+ function handleKeyDown(event) {
255
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
256
+ event.preventDefault();
257
+ void flushTaskSave();
258
+ return;
259
+ }
260
+ if (isEditableEventTarget(event.target)) {
261
+ return;
262
+ }
263
+ if (!event.metaKey &&
264
+ !event.ctrlKey &&
265
+ !event.altKey &&
266
+ event.key.toLowerCase() === "d") {
267
+ event.preventDefault();
268
+ toggleAnnotateModeFromShortcut();
269
+ return;
270
+ }
271
+ if (event.key === "Escape") {
272
+ clearHoverState();
273
+ setTraceNodeHover(null);
274
+ setAnnotateMode(false);
275
+ }
276
+ }
277
+ window.addEventListener("keydown", handleKeyDown);
278
+ return () => {
279
+ window.removeEventListener("keydown", handleKeyDown);
280
+ };
281
+ }, [flushTaskSave]);
282
+ useEffect(() => {
283
+ const updateCodeOpenMode = (event) => {
284
+ const target = frozenHoverTargetRef.current ?? hoverTargetRef.current;
285
+ const enabled = target !== null && (event.ctrlKey || event.metaKey);
286
+ if (enabled && target && !frozenHoverTargetRef.current) {
287
+ frozenHoverTargetRef.current = target;
288
+ setFrozenHoverTarget(target);
289
+ }
290
+ if (!enabled && frozenHoverTargetRef.current) {
291
+ frozenHoverTargetRef.current = null;
292
+ setFrozenHoverTarget(null);
293
+ }
294
+ codeOpenModeRef.current = enabled;
295
+ setCodeOpenMode(enabled);
296
+ };
297
+ window.addEventListener("keydown", updateCodeOpenMode);
298
+ window.addEventListener("keyup", updateCodeOpenMode);
299
+ return () => {
300
+ window.removeEventListener("keydown", updateCodeOpenMode);
301
+ window.removeEventListener("keyup", updateCodeOpenMode);
302
+ };
303
+ }, []);
304
+ function isInsideAnnotateRoot(target) {
305
+ return target instanceof Node && Boolean(annotateRootRef.current?.contains(target));
306
+ }
307
+ function clearHoverState() {
308
+ lastHoverElementRef.current = null;
309
+ hoverTargetRef.current = null;
310
+ frozenHoverTargetRef.current = null;
311
+ codeOpenModeRef.current = false;
312
+ setHoverTarget(null);
313
+ setFrozenHoverTarget(null);
314
+ setCodeOpenMode(false);
315
+ }
316
+ function toggleAnnotateModeFromShortcut() {
317
+ setAnnotateMode((current) => !current);
318
+ }
319
+ function toggleAnnotateModeFromToolbar() {
320
+ setAnnotateMode((current) => {
321
+ const next = !current;
322
+ if (next) {
323
+ openWindow("annotate");
324
+ }
325
+ return next;
326
+ });
327
+ }
328
+ function handleShowNativeTraceNodesChange(showNativeTraceNodes) {
329
+ clearHoverState();
330
+ setSettings((current) => ({
331
+ ...current,
332
+ showNativeTraceNodes
333
+ }));
334
+ }
335
+ function handleAnnotateTargetFromElement(element) {
336
+ if (taskStatusRef.current !== "ready") {
337
+ return false;
338
+ }
339
+ const selectableTarget = findSelectableTarget(element, document.body, null, settings.showNativeTraceNodes);
340
+ const trace = getAnnotateTargetTrace(selectableTarget);
341
+ if (!selectableTarget || !trace || isInsideAnnotateRoot(selectableTarget.element)) {
342
+ return false;
343
+ }
344
+ const result = registerAnnotateTarget(taskRef.current, { trace });
345
+ setTargetElementMatches((current) => [
346
+ ...current.filter((match) => match.targetId !== result.targetId),
347
+ {
348
+ targetId: result.targetId,
349
+ element: selectableTarget.element
350
+ }
351
+ ]);
352
+ openWindow("annotate");
353
+ commitTask(result.task, "immediate");
354
+ setActiveTargetId(result.targetId);
355
+ return true;
356
+ }
357
+ function handleActiveTargetChange(targetId) {
358
+ if (!taskRef.current.targets.some((target) => target.id === targetId)) {
359
+ return;
360
+ }
361
+ setTraceNodeHover(null);
362
+ setActiveTargetId(targetId);
363
+ }
364
+ function handleOpenTarget(targetId) {
365
+ if (!taskRef.current.targets.some((target) => target.id === targetId)) {
366
+ return;
367
+ }
368
+ setActiveTargetId(targetId);
369
+ openWindow("annotate");
370
+ }
371
+ function handleAddTargetComment(targetId) {
372
+ const target = taskRef.current.targets.find((candidate) => candidate.id === targetId);
373
+ if (!target) {
374
+ return;
375
+ }
376
+ commitTask(updateTargetComments(taskRef.current, targetId, [...target.comments, ""]), "immediate");
377
+ }
378
+ function handleTargetCommentChange(targetId, index, value) {
379
+ const target = taskRef.current.targets.find((candidate) => candidate.id === targetId);
380
+ if (!target) {
381
+ return;
382
+ }
383
+ const comments = target.comments.map((comment, commentIndex) => commentIndex === index ? value : comment);
384
+ commitTask(updateTargetComments(taskRef.current, targetId, comments), "debounced");
385
+ }
386
+ function handleDeleteTargetComment(targetId, index) {
387
+ const target = taskRef.current.targets.find((candidate) => candidate.id === targetId);
388
+ if (!target) {
389
+ return;
390
+ }
391
+ const comments = target.comments.filter((_, commentIndex) => commentIndex !== index);
392
+ commitTask(updateTargetComments(taskRef.current, targetId, comments), "immediate");
393
+ }
394
+ function handleDeleteTarget(targetId) {
395
+ const targetIndex = taskRef.current.targets.findIndex((target) => target.id === targetId);
396
+ if (targetIndex < 0) {
397
+ return;
398
+ }
399
+ const nextTargets = taskRef.current.targets.filter((target) => target.id !== targetId);
400
+ const nextActiveTarget = nextTargets[Math.min(targetIndex, nextTargets.length - 1)] ??
401
+ nextTargets[targetIndex - 1] ??
402
+ null;
403
+ setActiveTargetId((currentActiveTargetId) => currentActiveTargetId === targetId ? nextActiveTarget?.id ?? null : currentActiveTargetId);
404
+ setTraceNodeHover((currentHover) => currentHover?.targetId === targetId ? null : currentHover);
405
+ setTargetElementMatches((current) => current.filter((match) => match.targetId !== targetId));
406
+ commitTask(deleteTarget(taskRef.current, targetId), "immediate");
407
+ }
408
+ function handleResetTargets() {
409
+ if (taskRef.current.targets.length === 0) {
410
+ return;
411
+ }
412
+ setActiveTargetId(null);
413
+ setTraceNodeHover(null);
414
+ setTargetElementMatches([]);
415
+ commitTask(deleteAllTargets(taskRef.current), "immediate");
416
+ }
417
+ function handleAnnotateTraceNodeHover(targetId, node) {
418
+ setTraceNodeHover(node ? { source: "annotate", targetId, node } : null);
419
+ }
420
+ async function handleOpenCodeFromElement(element, mode = "element") {
421
+ if (mode === "frozen-first" && frozenHoverTargetRef.current) {
422
+ await handleOpenCodePath(frozenHoverTargetRef.current.path);
423
+ return true;
424
+ }
425
+ const selectableTarget = findSelectableTarget(element, document.body, null, settings.showNativeTraceNodes);
426
+ if (!selectableTarget) {
427
+ return false;
428
+ }
429
+ await handleOpenCodePath(selectableTarget.path);
430
+ return true;
431
+ }
432
+ async function handleOpenCodeFromElementAndExitAnnotate(element, mode) {
433
+ if (await handleOpenCodeFromElement(element, mode)) {
434
+ setAnnotateMode(false);
435
+ }
436
+ }
437
+ async function handleOpenCodePath(path) {
438
+ const response = await fetch("/__ui-annotate/code/open", {
439
+ method: "POST",
440
+ headers: {
441
+ "Content-Type": "application/json"
442
+ },
443
+ body: JSON.stringify({ path })
444
+ });
445
+ if (!response.ok) {
446
+ throw new Error(`UI Annotate code open failed with ${response.status}.`);
447
+ }
448
+ }
449
+ function handleGetAnnotateTraceNodeElement(targetId, node) {
450
+ const target = taskRef.current.targets.find((candidate) => candidate.id === targetId);
451
+ if (!target) {
452
+ return null;
453
+ }
454
+ return findElementForTraceNode(document.body, target, node, null, settings.showNativeTraceNodes);
455
+ }
456
+ const overlayHighlights = useMemo(() => {
457
+ const highlights = [];
458
+ const targetsById = new Map(task.targets.map((target) => [target.id, target]));
459
+ for (const match of targetElementMatches) {
460
+ const target = targetsById.get(match.targetId);
461
+ if (!target || isInsideAnnotateRoot(match.element)) {
462
+ continue;
463
+ }
464
+ highlights.push({
465
+ key: `annotate:${target.id}`,
466
+ element: match.element,
467
+ intent: "selected",
468
+ role: "target-primary",
469
+ label: target.id,
470
+ targetId: target.id,
471
+ comments: getVisibleComments(target.comments)
472
+ });
473
+ }
474
+ if (annotateMode && activeHoverTarget && !isInsideAnnotateRoot(activeHoverTarget.element)) {
475
+ const activeHoverTrace = getAnnotateTargetTrace(activeHoverTarget);
476
+ if (activeHoverTrace) {
477
+ const knownTarget = task.targets.find((target) => isSameTargetTrace(target.trace, activeHoverTrace));
478
+ highlights.push({
479
+ key: "hover",
480
+ element: activeHoverTarget.element,
481
+ intent: "active",
482
+ role: "annotate-hover",
483
+ label: knownTarget?.id,
484
+ ...(knownTarget
485
+ ? {
486
+ targetId: knownTarget.id,
487
+ comments: getVisibleComments(knownTarget.comments)
488
+ }
489
+ : {})
490
+ });
491
+ }
492
+ }
493
+ if (traceNodeHover) {
494
+ const element = handleGetAnnotateTraceNodeElement(traceNodeHover.targetId, traceNodeHover.node);
495
+ if (element && !isInsideAnnotateRoot(element)) {
496
+ highlights.push({
497
+ key: `trace-hover:${traceNodeHover.targetId}:${traceNodeHover.node.kind}:${traceNodeHover.node.frameIndex}:${traceNodeHover.node.kind === "call" ? traceNodeHover.node.callIndex : "file"}`,
498
+ element,
499
+ intent: "active",
500
+ role: "annotate-hover",
501
+ label: traceNodeHover.targetId
502
+ });
503
+ }
504
+ }
505
+ return highlights;
506
+ }, [
507
+ activeHoverTarget,
508
+ annotateMode,
509
+ settings.showNativeTraceNodes,
510
+ targetElementMatches,
511
+ task.targets,
512
+ traceNodeHover
513
+ ]);
514
+ return (_jsx(PortalContainerProvider, { container: portalContainer, zIndex: ANNOTATE_PORTAL_Z_INDEX, children: _jsx(TooltipProvider, { children: _jsxs("div", { className: showCodeOpenCursor
515
+ ? "ui-annotate-root pointer-events-none !cursor-pointer text-foreground [&_*]:!cursor-pointer"
516
+ : "ui-annotate-root pointer-events-none text-foreground", children: [_jsx(SelectionOverlay, { highlights: overlayHighlights, onEditTarget: handleOpenTarget, onDeleteAnnotateTarget: handleDeleteTarget }), _jsx(AnnotateToolbar, { annotateMode: annotateMode, position: toolbarPosition, onBeginDrag: beginToolbarDrag, onToggleAnnotateMode: toggleAnnotateModeFromToolbar, onOpenWindow: openWindow }), _jsx(AnnotateWindows, { activeTargetId: activeAnnotateTarget?.id ?? null, saveError: saveError, saveStatus: saveStatus, settings: settings, targets: task.targets, taskError: taskError, taskStatus: taskStatus, windows: windows, onActiveTargetChange: handleActiveTargetChange, onAddComment: handleAddTargetComment, onBeginMove: beginWindowMove, onBeginResize: beginWindowResize, onChangeComment: handleTargetCommentChange, onClose: closeWindow, onDeleteComment: handleDeleteTargetComment, onDeleteTarget: handleDeleteTarget, onResetTargets: handleResetTargets, onGetAnnotateTraceNodeElement: handleGetAnnotateTraceNodeElement, onHoverAnnotateTraceNode: handleAnnotateTraceNodeHover, onOpenCodePath: (path) => void handleOpenCodePath(path), onShowNativeTraceNodesChange: handleShowNativeTraceNodesChange })] }) }) }));
517
+ }
518
+ function getAnnotateTargetTrace(target) {
519
+ return target?.annotateTrace ?? target?.trace ?? null;
520
+ }
521
+ function getVisibleComments(comments) {
522
+ return comments.map((comment) => comment.trim()).filter(Boolean);
523
+ }
524
+ function createTargetMatchKey(targets) {
525
+ return targets
526
+ .map((target) => `${target.id}:${JSON.stringify(target.trace)}`)
527
+ .join("|");
528
+ }
529
+ const CODE_OPEN_CURSOR_STYLE_ID = "ui-annotate-code-open-cursor-style";
530
+ function ensureCodeOpenCursorStyle() {
531
+ const existingStyle = document.getElementById(CODE_OPEN_CURSOR_STYLE_ID);
532
+ if (existingStyle instanceof HTMLStyleElement) {
533
+ return existingStyle;
534
+ }
535
+ const styleElement = document.createElement("style");
536
+ styleElement.id = CODE_OPEN_CURSOR_STYLE_ID;
537
+ styleElement.textContent = "body, body * { cursor: pointer !important; }";
538
+ document.head.append(styleElement);
539
+ return styleElement;
540
+ }
@@ -0,0 +1,5 @@
1
+ export { UiAnnotate } from "./app/UiAnnotate.js";
2
+ export { AnnotateWindow } from "./windows/AnnotateWindow.js";
3
+ export { TargetTraceTree } from "./windows/TargetTraceTree.js";
4
+ export type { TraceNodeElementResolver } from "./windows/TargetTraceTree.js";
5
+ export * from "./shared/annotate-types.js";
@@ -0,0 +1,4 @@
1
+ export { UiAnnotate } from "./app/UiAnnotate.js";
2
+ export { AnnotateWindow } from "./windows/AnnotateWindow.js";
3
+ export { TargetTraceTree } from "./windows/TargetTraceTree.js";
4
+ export * from "./shared/annotate-types.js";
@@ -0,0 +1,10 @@
1
+ import { type AnnotateTarget, type TraceFrame, type TraceNodeRef } from "../../protocol/task-model.js";
2
+ import type { SelectableTarget } from "../shared/annotate-types.js";
3
+ export declare function findSelectableTarget(start: Element, previewRoot: HTMLElement, annotateFile: string | null, showNativeTraceNodes: boolean): SelectableTarget | null;
4
+ export declare function createTraceFromSelectableTarget(target: SelectableTarget): TraceFrame[] | null;
5
+ export declare function createTargetName(target: SelectableTarget): string;
6
+ export declare function findElementForTarget(previewRoot: HTMLElement, target: AnnotateTarget, annotateFile: string | null, showNativeTraceNodes: boolean): Element | null;
7
+ export declare function findElementForTraceNode(previewRoot: HTMLElement, target: AnnotateTarget, node: TraceNodeRef, annotateFile: string | null, showNativeTraceNodes: boolean): Element | null;
8
+ export declare function findElementForTraceNodeInTrace(previewRoot: HTMLElement, trace: TraceFrame[], node: TraceNodeRef, annotateFile: string | null, showNativeTraceNodes: boolean, contextElement?: Element): Element | null;
9
+ export declare function getHighlightRole(annotateFile: string, target: AnnotateTarget): "target-primary";
10
+ export declare function isEditableEventTarget(target: EventTarget | null): boolean;