@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,337 @@
1
+ import { INSPECTOR_ATTRIBUTE } from "../shared/annotate-constants.js";
2
+ import { getTargetSourceLocation, isSameTargetTrace, normalizeTraceRoles, parseInspectorPath } from "../../protocol/task-model.js";
3
+ export function findSelectableTarget(start, previewRoot, annotateFile, showNativeTraceNodes) {
4
+ const inspectedElement = start.closest(`[${INSPECTOR_ATTRIBUTE}]`);
5
+ const fiberFrames = getFiberSourceFrames(start, previewRoot, annotateFile, true);
6
+ if (!inspectedElement || !previewRoot.contains(inspectedElement)) {
7
+ return findSelectableFiberTarget(start, previewRoot, annotateFile, showNativeTraceNodes, fiberFrames);
8
+ }
9
+ const raw = inspectedElement.getAttribute(INSPECTOR_ATTRIBUTE);
10
+ const path = raw ? parseInspectorLocation(raw) : null;
11
+ if (!path) {
12
+ return findSelectableFiberTarget(start, previewRoot, annotateFile, showNativeTraceNodes, fiberFrames);
13
+ }
14
+ const targetFrame = {
15
+ name: inspectedElement.tagName.toLowerCase(),
16
+ path,
17
+ native: true
18
+ };
19
+ const traces = createTargetTraces(targetFrame, fiberFrames, annotateFile, showNativeTraceNodes);
20
+ return {
21
+ element: inspectedElement,
22
+ path,
23
+ name: targetFrame.name,
24
+ elementName: inspectedElement.tagName.toLowerCase(),
25
+ trace: traces.trace,
26
+ annotateTrace: traces.annotateTrace
27
+ };
28
+ }
29
+ export function createTraceFromSelectableTarget(target) {
30
+ return target.annotateTrace ?? target.trace ?? null;
31
+ }
32
+ export function createTargetName(target) {
33
+ return target.name;
34
+ }
35
+ export function findElementForTarget(previewRoot, target, annotateFile, showNativeTraceNodes) {
36
+ return (findElementForTargetByTrace(previewRoot, target, annotateFile, showNativeTraceNodes) ??
37
+ findElementForTargetByTrace(previewRoot, target, annotateFile, !showNativeTraceNodes));
38
+ }
39
+ export function findElementForTraceNode(previewRoot, target, node, annotateFile, showNativeTraceNodes) {
40
+ const location = getTraceNodeLocation(target, node);
41
+ if (!location) {
42
+ return null;
43
+ }
44
+ // First locate the selected target as context, so matching names elsewhere
45
+ // in the Annotate UI do not steal the trace hover highlight.
46
+ const targetElement = findElementForTarget(previewRoot, target, annotateFile, showNativeTraceNodes) ??
47
+ findElementForTarget(previewRoot, target, annotateFile, !showNativeTraceNodes);
48
+ if (targetElement) {
49
+ const scoped = findElementForTraceLocationInContext(previewRoot, location, targetElement, annotateFile, showNativeTraceNodes);
50
+ if (scoped)
51
+ return scoped;
52
+ const scopedFlip = findElementForTraceLocationInContext(previewRoot, location, targetElement, annotateFile, !showNativeTraceNodes);
53
+ if (scopedFlip)
54
+ return scopedFlip;
55
+ }
56
+ return (findElementForTraceLocation(previewRoot, location, annotateFile, showNativeTraceNodes) ??
57
+ findElementForTraceLocation(previewRoot, location, annotateFile, !showNativeTraceNodes));
58
+ }
59
+ export function findElementForTraceNodeInTrace(previewRoot, trace, node, annotateFile, showNativeTraceNodes, contextElement) {
60
+ const location = getTraceLocation(trace, node);
61
+ if (!location) {
62
+ return null;
63
+ }
64
+ if (contextElement) {
65
+ const scoped = findElementForAnyTraceLocationInContext(previewRoot, location, contextElement, annotateFile, showNativeTraceNodes);
66
+ if (scoped)
67
+ return scoped;
68
+ const scopedFlip = findElementForAnyTraceLocationInContext(previewRoot, location, contextElement, annotateFile, !showNativeTraceNodes);
69
+ if (scopedFlip)
70
+ return scopedFlip;
71
+ }
72
+ return (findElementForAnyTraceLocation(previewRoot, location, annotateFile, showNativeTraceNodes) ??
73
+ findElementForAnyTraceLocation(previewRoot, location, annotateFile, !showNativeTraceNodes));
74
+ }
75
+ export function getHighlightRole(annotateFile, target) {
76
+ return getTargetSourceLocation(target).relativePath === annotateFile
77
+ ? "target-primary"
78
+ : "target-primary";
79
+ }
80
+ export function isEditableEventTarget(target) {
81
+ if (!(target instanceof Element)) {
82
+ return false;
83
+ }
84
+ return Boolean(target.closest("input, textarea, select, [contenteditable='true'], [role='textbox']"));
85
+ }
86
+ function findSelectableFiberTarget(start, previewRoot, annotateFile, showNativeTraceNodes, fiberFrames) {
87
+ const fiber = getReactFiberFromElement(start);
88
+ const skipInternalPathFilter = annotateFile === null;
89
+ let current = fiber;
90
+ while (current) {
91
+ const props = getFiberProps(current);
92
+ const rawInspector = readStringProp(props, INSPECTOR_ATTRIBUTE);
93
+ const path = rawInspector ? parseInspectorLocation(rawInspector) : null;
94
+ if (path &&
95
+ (skipInternalPathFilter || !isUiAnnotateInternalPath(parseInspectorPath(path).relativePath))) {
96
+ const name = getFiberName(current);
97
+ const targetFrame = {
98
+ name,
99
+ path,
100
+ native: isNativeHostFiber(current)
101
+ };
102
+ const traces = createTargetTraces(targetFrame, fiberFrames, annotateFile, showNativeTraceNodes);
103
+ return {
104
+ element: previewRoot.contains(start) ? start : previewRoot,
105
+ path,
106
+ name,
107
+ elementName: start.tagName.toLowerCase(),
108
+ trace: traces.trace,
109
+ annotateTrace: traces.annotateTrace
110
+ };
111
+ }
112
+ current = current.return ?? null;
113
+ }
114
+ return null;
115
+ }
116
+ function parseInspectorLocation(raw) {
117
+ const match = /^(.*):(\d+):(\d+)$/.exec(raw);
118
+ if (!match) {
119
+ return null;
120
+ }
121
+ return raw;
122
+ }
123
+ function findElementForTraceLocationInContext(previewRoot, location, contextElement, annotateFile, showNativeTraceNodes) {
124
+ for (const element of getTargetCandidateElements(previewRoot)) {
125
+ if (element !== contextElement &&
126
+ !contextElement.contains(element) &&
127
+ !element.contains(contextElement)) {
128
+ continue;
129
+ }
130
+ const selectableTarget = findSelectableTarget(element, previewRoot, annotateFile, showNativeTraceNodes);
131
+ const trace = selectableTarget ? createTraceFromSelectableTarget(selectableTarget) : null;
132
+ if (selectableTarget &&
133
+ trace &&
134
+ traceIncludesLocation(trace, location)) {
135
+ return selectableTarget.element;
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ function findElementForAnyTraceLocationInContext(previewRoot, location, contextElement, annotateFile, showNativeTraceNodes) {
141
+ for (const element of getTargetCandidateElements(previewRoot)) {
142
+ if (element !== contextElement &&
143
+ !contextElement.contains(element) &&
144
+ !element.contains(contextElement)) {
145
+ continue;
146
+ }
147
+ const selectableTarget = findSelectableTarget(element, previewRoot, annotateFile, showNativeTraceNodes);
148
+ if (selectableTargetIncludesLocation(selectableTarget, location)) {
149
+ return selectableTarget.element;
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ function findElementForTargetByTrace(previewRoot, target, annotateFile, showNativeTraceNodes) {
155
+ for (const element of getTargetCandidateElements(previewRoot)) {
156
+ const selectableTarget = findSelectableTarget(element, previewRoot, annotateFile, showNativeTraceNodes);
157
+ const trace = selectableTarget ? createTraceFromSelectableTarget(selectableTarget) : null;
158
+ if (selectableTarget &&
159
+ trace &&
160
+ isSameTargetTrace(target.trace, trace)) {
161
+ return selectableTarget.element;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function findElementForTraceLocation(previewRoot, location, annotateFile, showNativeTraceNodes) {
167
+ for (const element of getTargetCandidateElements(previewRoot)) {
168
+ const selectableTarget = findSelectableTarget(element, previewRoot, annotateFile, showNativeTraceNodes);
169
+ const trace = selectableTarget ? createTraceFromSelectableTarget(selectableTarget) : null;
170
+ if (selectableTarget &&
171
+ trace &&
172
+ traceIncludesLocation(trace, location)) {
173
+ return selectableTarget.element;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+ function findElementForAnyTraceLocation(previewRoot, location, annotateFile, showNativeTraceNodes) {
179
+ for (const element of getTargetCandidateElements(previewRoot)) {
180
+ const selectableTarget = findSelectableTarget(element, previewRoot, annotateFile, showNativeTraceNodes);
181
+ if (selectableTargetIncludesLocation(selectableTarget, location)) {
182
+ return selectableTarget.element;
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ function getTraceNodeLocation(target, node) {
188
+ return getTraceLocation(target.trace, node);
189
+ }
190
+ function getTraceLocation(trace, node) {
191
+ const frame = trace[node.frameIndex];
192
+ const call = node.kind === "file"
193
+ ? frame?.calls[0]
194
+ : frame?.calls[node.callIndex];
195
+ if (!frame || !call) {
196
+ return null;
197
+ }
198
+ return {
199
+ file: frame.file,
200
+ line: call.line,
201
+ component: call.component
202
+ };
203
+ }
204
+ function selectableTargetIncludesLocation(target, location) {
205
+ return Boolean(target &&
206
+ ((target.trace && traceIncludesLocation(target.trace, location)) ||
207
+ (target.annotateTrace && traceIncludesLocation(target.annotateTrace, location))));
208
+ }
209
+ function traceIncludesLocation(trace, location) {
210
+ return trace.some((frame) => frame.file === location.file &&
211
+ frame.calls.some((call) => call.line === location.line && call.component === location.component));
212
+ }
213
+ function getTargetCandidateElements(root) {
214
+ return [
215
+ ...(root.hasAttribute(INSPECTOR_ATTRIBUTE) ? [root] : []),
216
+ ...root.querySelectorAll(`[${INSPECTOR_ATTRIBUTE}]`)
217
+ ];
218
+ }
219
+ function getReactFiberFromElement(element) {
220
+ const fiberKey = Object.keys(element).find((key) => {
221
+ return key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$");
222
+ });
223
+ if (!fiberKey) {
224
+ return null;
225
+ }
226
+ return element[fiberKey] ?? null;
227
+ }
228
+ function getFiberSourceFrames(element, stopElement, annotateFile, includeNativeNodes) {
229
+ const frames = [];
230
+ const skipInternalPathFilter = annotateFile === null;
231
+ let current = getReactFiberFromElement(element);
232
+ while (current) {
233
+ if (current.stateNode === stopElement) {
234
+ break;
235
+ }
236
+ const props = getFiberProps(current);
237
+ const rawInspector = readStringProp(props, INSPECTOR_ATTRIBUTE);
238
+ const path = rawInspector ? parseInspectorLocation(rawInspector) : null;
239
+ if (path &&
240
+ (includeNativeNodes || !isNativeHostFiber(current)) &&
241
+ (skipInternalPathFilter || !isUiAnnotateInternalPath(parseInspectorPath(path).relativePath))) {
242
+ frames.push({
243
+ name: getFiberName(current),
244
+ path,
245
+ native: isNativeHostFiber(current)
246
+ });
247
+ }
248
+ current = current.return ?? null;
249
+ }
250
+ return frames;
251
+ }
252
+ function createTargetTraces(targetFrame, sourceFrames, annotateFile, showNativeTraceNodes) {
253
+ const frames = ensureNearestFrame(sourceFrames, targetFrame).reverse();
254
+ const annotateTrace = createTraceFromSourceFrames(filterSourceFrames(frames, showNativeTraceNodes));
255
+ if (!annotateFile) {
256
+ return { annotateTrace };
257
+ }
258
+ const entryIndex = frames.findIndex((frame) => parseInspectorPath(frame.path).relativePath === annotateFile);
259
+ if (entryIndex < 0) {
260
+ return { annotateTrace };
261
+ }
262
+ return {
263
+ trace: createTraceFromSourceFrames(filterSourceFrames(frames.slice(entryIndex), showNativeTraceNodes)),
264
+ annotateTrace
265
+ };
266
+ }
267
+ function ensureNearestFrame(sourceFrames, targetFrame) {
268
+ const nearestFrame = sourceFrames[0];
269
+ if (nearestFrame && nearestFrame.path === targetFrame.path && nearestFrame.name === targetFrame.name) {
270
+ return [...sourceFrames];
271
+ }
272
+ return [targetFrame, ...sourceFrames];
273
+ }
274
+ function filterSourceFrames(frames, showNativeTraceNodes) {
275
+ if (showNativeTraceNodes || frames.length <= 2) {
276
+ return frames;
277
+ }
278
+ return frames.filter((frame, index) => index === 0 || index === frames.length - 1 || !frame.native);
279
+ }
280
+ function createTraceFromSourceFrames(frames) {
281
+ const trace = [];
282
+ for (const frame of frames) {
283
+ const location = parseInspectorPath(frame.path);
284
+ const previous = trace[trace.length - 1];
285
+ if (!previous || previous.file !== location.relativePath) {
286
+ trace.push({
287
+ file: location.relativePath,
288
+ calls: []
289
+ });
290
+ }
291
+ trace[trace.length - 1].calls.push({
292
+ line: location.line,
293
+ component: frame.name
294
+ });
295
+ }
296
+ return trace.length > 0 ? normalizeTraceRoles(trace) : undefined;
297
+ }
298
+ function isNativeHostFiber(fiber) {
299
+ return typeof (fiber.elementType ?? fiber.type) === "string";
300
+ }
301
+ function isUiAnnotateInternalPath(relativePath) {
302
+ return relativePath.includes("/runtime/");
303
+ }
304
+ function getFiberProps(fiber) {
305
+ const props = fiber.pendingProps ?? fiber.memoizedProps;
306
+ if (typeof props === "object" && props !== null && !Array.isArray(props)) {
307
+ return props;
308
+ }
309
+ return {};
310
+ }
311
+ function readStringProp(props, name) {
312
+ return typeof props[name] === "string" ? props[name] : undefined;
313
+ }
314
+ function getFiberName(fiber) {
315
+ const type = fiber.elementType ?? fiber.type;
316
+ if (typeof type === "string") {
317
+ return type;
318
+ }
319
+ if (typeof type === "function") {
320
+ const namedType = type;
321
+ return typeof namedType.displayName === "string"
322
+ ? namedType.displayName
323
+ : typeof namedType.name === "string"
324
+ ? namedType.name
325
+ : "Component";
326
+ }
327
+ if (typeof type === "object" && type !== null) {
328
+ const namedType = type;
329
+ if (typeof namedType.displayName === "string") {
330
+ return namedType.displayName;
331
+ }
332
+ if (typeof namedType.name === "string") {
333
+ return namedType.name;
334
+ }
335
+ }
336
+ return "Component";
337
+ }
@@ -0,0 +1,9 @@
1
+ import type { AnnotateSettings, AnnotateWindowMap, AnnotateWindowState, ToolbarPosition } from "../shared/annotate-types.js";
2
+ import type { AnnotateWindowResizeDirection } from "../windows/AnnotateWindowFrame.js";
3
+ export declare function readWindowState(): AnnotateWindowMap;
4
+ export declare function resizeWindowState(currentWindow: AnnotateWindowState, startWindow: AnnotateWindowState, direction: AnnotateWindowResizeDirection, deltaX: number, deltaY: number): AnnotateWindowState;
5
+ export declare function clampWindowState(windowState: AnnotateWindowState): AnnotateWindowState;
6
+ export declare function getTopWindowZ(windows: AnnotateWindowMap): number;
7
+ export declare function readToolbarPosition(): ToolbarPosition;
8
+ export declare function clampToolbarPosition(position: ToolbarPosition): ToolbarPosition;
9
+ export declare function readAnnotateSettings(): AnnotateSettings;
@@ -0,0 +1,134 @@
1
+ import { ANNOTATE_SETTINGS_STORAGE_KEY, ANNOTATE_TOOLBAR_STORAGE_KEY, ANNOTATE_WINDOW_STORAGE_KEY, TOOLBAR_HEIGHT, TOOLBAR_WIDTH, WINDOW_MAX_SIZE_INSET, WINDOW_MIN_HEIGHT, WINDOW_MIN_WIDTH, WINDOW_VIEWPORT_MARGIN } from "../shared/annotate-constants.js";
2
+ export function readWindowState() {
3
+ const fallback = getDefaultWindowState();
4
+ try {
5
+ const raw = window.localStorage.getItem(ANNOTATE_WINDOW_STORAGE_KEY);
6
+ if (!raw) {
7
+ return fallback;
8
+ }
9
+ const parsed = JSON.parse(raw);
10
+ return {
11
+ annotate: clampWindowState({ ...fallback.annotate, ...parsed.annotate }),
12
+ settings: clampWindowState({ ...fallback.settings, ...parsed.settings })
13
+ };
14
+ }
15
+ catch {
16
+ return fallback;
17
+ }
18
+ }
19
+ export function resizeWindowState(currentWindow, startWindow, direction, deltaX, deltaY) {
20
+ const nextWindow = { ...currentWindow };
21
+ const maxWidth = getMaxWindowWidth();
22
+ const maxHeight = getMaxWindowHeight();
23
+ if (direction.horizontal === 1) {
24
+ const widthLimit = Math.min(maxWidth, window.innerWidth - WINDOW_VIEWPORT_MARGIN - startWindow.x);
25
+ nextWindow.x = startWindow.x;
26
+ nextWindow.width = clampNumber(startWindow.width + deltaX, WINDOW_MIN_WIDTH, Math.max(WINDOW_MIN_WIDTH, widthLimit));
27
+ }
28
+ else if (direction.horizontal === -1) {
29
+ const right = startWindow.x + startWindow.width;
30
+ const widthLimit = Math.min(maxWidth, right - WINDOW_VIEWPORT_MARGIN);
31
+ nextWindow.width = clampNumber(startWindow.width - deltaX, WINDOW_MIN_WIDTH, Math.max(WINDOW_MIN_WIDTH, widthLimit));
32
+ nextWindow.x = right - nextWindow.width;
33
+ }
34
+ if (direction.vertical === 1) {
35
+ const heightLimit = Math.min(maxHeight, window.innerHeight - WINDOW_VIEWPORT_MARGIN - startWindow.y);
36
+ nextWindow.y = startWindow.y;
37
+ nextWindow.height = clampNumber(startWindow.height + deltaY, WINDOW_MIN_HEIGHT, Math.max(WINDOW_MIN_HEIGHT, heightLimit));
38
+ }
39
+ else if (direction.vertical === -1) {
40
+ const bottom = startWindow.y + startWindow.height;
41
+ const heightLimit = Math.min(maxHeight, bottom - WINDOW_VIEWPORT_MARGIN);
42
+ nextWindow.height = clampNumber(startWindow.height - deltaY, WINDOW_MIN_HEIGHT, Math.max(WINDOW_MIN_HEIGHT, heightLimit));
43
+ nextWindow.y = bottom - nextWindow.height;
44
+ }
45
+ return clampWindowState(nextWindow);
46
+ }
47
+ export function clampWindowState(windowState) {
48
+ const width = clampNumber(windowState.width, WINDOW_MIN_WIDTH, getMaxWindowWidth());
49
+ const height = clampNumber(windowState.height, WINDOW_MIN_HEIGHT, getMaxWindowHeight());
50
+ return {
51
+ ...windowState,
52
+ width,
53
+ height,
54
+ x: clampNumber(windowState.x, WINDOW_VIEWPORT_MARGIN, Math.max(WINDOW_VIEWPORT_MARGIN, window.innerWidth - width - WINDOW_VIEWPORT_MARGIN)),
55
+ y: clampNumber(windowState.y, WINDOW_VIEWPORT_MARGIN, Math.max(WINDOW_VIEWPORT_MARGIN, window.innerHeight - height - WINDOW_VIEWPORT_MARGIN))
56
+ };
57
+ }
58
+ export function getTopWindowZ(windows) {
59
+ return Math.max(...Object.values(windows).map((windowState) => windowState.zIndex));
60
+ }
61
+ export function readToolbarPosition() {
62
+ const fallback = getDefaultToolbarPosition();
63
+ try {
64
+ const raw = window.localStorage.getItem(ANNOTATE_TOOLBAR_STORAGE_KEY);
65
+ if (!raw) {
66
+ return fallback;
67
+ }
68
+ return clampToolbarPosition({
69
+ ...fallback,
70
+ ...JSON.parse(raw)
71
+ });
72
+ }
73
+ catch {
74
+ return fallback;
75
+ }
76
+ }
77
+ export function clampToolbarPosition(position) {
78
+ return {
79
+ x: Math.min(Math.max(position.x, 8), Math.max(8, window.innerWidth - TOOLBAR_WIDTH - 8)),
80
+ y: Math.min(Math.max(position.y, 8), Math.max(8, window.innerHeight - TOOLBAR_HEIGHT - 8))
81
+ };
82
+ }
83
+ export function readAnnotateSettings() {
84
+ const fallback = {
85
+ showNativeTraceNodes: false
86
+ };
87
+ try {
88
+ const raw = window.localStorage.getItem(ANNOTATE_SETTINGS_STORAGE_KEY);
89
+ if (!raw) {
90
+ return fallback;
91
+ }
92
+ const parsed = JSON.parse(raw);
93
+ return {
94
+ showNativeTraceNodes: parsed.showNativeTraceNodes === true ||
95
+ parsed.showFullComponentStack === true
96
+ };
97
+ }
98
+ catch {
99
+ return fallback;
100
+ }
101
+ }
102
+ function getDefaultWindowState() {
103
+ return {
104
+ annotate: createDefaultWindow("annotate", 24),
105
+ settings: createDefaultWindow("settings", 84)
106
+ };
107
+ }
108
+ function createDefaultWindow(id, offset) {
109
+ const width = id === "settings" ? 360 : 380;
110
+ const height = id === "settings" ? 180 : 560;
111
+ return clampWindowState({
112
+ open: false,
113
+ x: Math.max(16, window.innerWidth - width - offset),
114
+ y: 64 + offset / 2,
115
+ width,
116
+ height,
117
+ zIndex: 10001
118
+ });
119
+ }
120
+ function getMaxWindowWidth() {
121
+ return Math.max(WINDOW_MIN_WIDTH, window.innerWidth - WINDOW_MAX_SIZE_INSET);
122
+ }
123
+ function getMaxWindowHeight() {
124
+ return Math.max(WINDOW_MIN_HEIGHT, window.innerHeight - WINDOW_MAX_SIZE_INSET);
125
+ }
126
+ function getDefaultToolbarPosition() {
127
+ return {
128
+ x: Math.round((window.innerWidth - TOOLBAR_WIDTH) / 2),
129
+ y: Math.max(16, window.innerHeight - TOOLBAR_HEIGHT - 20)
130
+ };
131
+ }
132
+ function clampNumber(value, min, max) {
133
+ return Math.min(Math.max(value, min), max);
134
+ }
@@ -0,0 +1,17 @@
1
+ import { type PointerEvent as ReactPointerEvent } from "react";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+ import type { AnnotateWindowId, AnnotateWindowMap, ToolbarPosition, AnnotateSettings } from "../shared/annotate-types.js";
4
+ import type { AnnotateWindowResizeDirection } from "../windows/AnnotateWindowFrame.js";
5
+ type UseAnnotateLayoutResult = {
6
+ beginToolbarDrag: (event: ReactPointerEvent<HTMLDivElement>) => void;
7
+ beginWindowMove: (id: AnnotateWindowId, event: ReactPointerEvent<HTMLElement>) => void;
8
+ beginWindowResize: (id: AnnotateWindowId, direction: AnnotateWindowResizeDirection, event: ReactPointerEvent<HTMLDivElement>) => void;
9
+ closeWindow: (id: AnnotateWindowId) => void;
10
+ openWindow: (id: AnnotateWindowId) => void;
11
+ setSettings: Dispatch<SetStateAction<AnnotateSettings>>;
12
+ settings: AnnotateSettings;
13
+ toolbarPosition: ToolbarPosition;
14
+ windows: AnnotateWindowMap;
15
+ };
16
+ export declare function useAnnotateLayout(): UseAnnotateLayoutResult;
17
+ export {};
@@ -0,0 +1,147 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { ANNOTATE_SETTINGS_STORAGE_KEY, ANNOTATE_TOOLBAR_STORAGE_KEY, ANNOTATE_WINDOW_STORAGE_KEY } from "../shared/annotate-constants.js";
3
+ import { clampToolbarPosition, clampWindowState, getTopWindowZ, readToolbarPosition, readWindowState, readAnnotateSettings, resizeWindowState } from "./annotate-storage.js";
4
+ export function useAnnotateLayout() {
5
+ const [windows, setWindows] = useState(() => readWindowState());
6
+ const [toolbarPosition, setToolbarPosition] = useState(() => readToolbarPosition());
7
+ const [settings, setSettings] = useState(() => readAnnotateSettings());
8
+ const gestureRef = useRef(null);
9
+ const nextWindowZRef = useRef(getTopWindowZ(windows) + 1);
10
+ useEffect(() => {
11
+ window.localStorage.setItem(ANNOTATE_WINDOW_STORAGE_KEY, JSON.stringify(windows));
12
+ }, [windows]);
13
+ useEffect(() => {
14
+ window.localStorage.setItem(ANNOTATE_TOOLBAR_STORAGE_KEY, JSON.stringify(toolbarPosition));
15
+ }, [toolbarPosition]);
16
+ useEffect(() => {
17
+ window.localStorage.setItem(ANNOTATE_SETTINGS_STORAGE_KEY, JSON.stringify(settings));
18
+ }, [settings]);
19
+ useEffect(() => {
20
+ function handlePointerMove(event) {
21
+ const gesture = gestureRef.current;
22
+ if (!gesture) {
23
+ return;
24
+ }
25
+ event.preventDefault();
26
+ if (gesture.kind === "toolbar") {
27
+ setToolbarPosition(clampToolbarPosition({
28
+ x: gesture.toolbar.x + event.clientX - gesture.startX,
29
+ y: gesture.toolbar.y + event.clientY - gesture.startY
30
+ }));
31
+ return;
32
+ }
33
+ setWindows((current) => {
34
+ const currentWindow = current[gesture.id];
35
+ const nextWindow = gesture.kind === "window-move"
36
+ ? {
37
+ ...currentWindow,
38
+ x: gesture.window.x + event.clientX - gesture.startX,
39
+ y: gesture.window.y + event.clientY - gesture.startY
40
+ }
41
+ : resizeWindowState(currentWindow, gesture.window, gesture.direction, event.clientX - gesture.startX, event.clientY - gesture.startY);
42
+ return {
43
+ ...current,
44
+ [gesture.id]: clampWindowState(nextWindow)
45
+ };
46
+ });
47
+ }
48
+ function handlePointerUp() {
49
+ gestureRef.current = null;
50
+ }
51
+ function handleResize() {
52
+ setToolbarPosition((current) => clampToolbarPosition(current));
53
+ setWindows((current) => {
54
+ const nextWindows = { ...current };
55
+ for (const id of Object.keys(nextWindows)) {
56
+ nextWindows[id] = clampWindowState(nextWindows[id]);
57
+ }
58
+ return nextWindows;
59
+ });
60
+ }
61
+ window.addEventListener("pointermove", handlePointerMove);
62
+ window.addEventListener("pointerup", handlePointerUp);
63
+ window.addEventListener("resize", handleResize);
64
+ return () => {
65
+ window.removeEventListener("pointermove", handlePointerMove);
66
+ window.removeEventListener("pointerup", handlePointerUp);
67
+ window.removeEventListener("resize", handleResize);
68
+ };
69
+ }, []);
70
+ function openWindow(id) {
71
+ setWindows((current) => {
72
+ const zIndex = nextWindowZRef.current + 1;
73
+ nextWindowZRef.current = zIndex;
74
+ return {
75
+ ...current,
76
+ [id]: {
77
+ ...current[id],
78
+ open: true,
79
+ zIndex
80
+ }
81
+ };
82
+ });
83
+ }
84
+ function closeWindow(id) {
85
+ setWindows((current) => ({
86
+ ...current,
87
+ [id]: {
88
+ ...current[id],
89
+ open: false
90
+ }
91
+ }));
92
+ }
93
+ function beginWindowMove(id, event) {
94
+ if (event.button !== 0) {
95
+ return;
96
+ }
97
+ event.currentTarget.setPointerCapture(event.pointerId);
98
+ openWindow(id);
99
+ gestureRef.current = {
100
+ kind: "window-move",
101
+ id,
102
+ startX: event.clientX,
103
+ startY: event.clientY,
104
+ window: windows[id]
105
+ };
106
+ }
107
+ function beginWindowResize(id, direction, event) {
108
+ if (event.button !== 0) {
109
+ return;
110
+ }
111
+ event.preventDefault();
112
+ event.stopPropagation();
113
+ event.currentTarget.setPointerCapture(event.pointerId);
114
+ openWindow(id);
115
+ gestureRef.current = {
116
+ kind: "window-resize",
117
+ id,
118
+ direction,
119
+ startX: event.clientX,
120
+ startY: event.clientY,
121
+ window: windows[id]
122
+ };
123
+ }
124
+ function beginToolbarDrag(event) {
125
+ if (event.button !== 0 || event.target instanceof Element && event.target.closest("button")) {
126
+ return;
127
+ }
128
+ event.currentTarget.setPointerCapture(event.pointerId);
129
+ gestureRef.current = {
130
+ kind: "toolbar",
131
+ startX: event.clientX,
132
+ startY: event.clientY,
133
+ toolbar: toolbarPosition
134
+ };
135
+ }
136
+ return {
137
+ beginToolbarDrag,
138
+ beginWindowMove,
139
+ beginWindowResize,
140
+ closeWindow,
141
+ openWindow,
142
+ setSettings,
143
+ settings,
144
+ toolbarPosition,
145
+ windows
146
+ };
147
+ }
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from "react";
2
+ import type { OverlayHighlight } from "../shared/annotate-types.js";
3
+ export declare function SelectionOverlay({ highlights, onEditTarget, onDeleteAnnotateTarget }: {
4
+ highlights: OverlayHighlight[];
5
+ onEditTarget(targetId: string): void;
6
+ onDeleteAnnotateTarget(targetId: string): void;
7
+ }): ReactNode;