nudge-ui 0.2.0 → 0.2.2

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 (231) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +878 -11
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +63 -11
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +4 -3
  9. package/dist/hosts/environment.d.ts +15 -0
  10. package/dist/hosts/environment.js +16 -0
  11. package/dist/hosts/next/sidecar.d.ts +4 -0
  12. package/dist/hosts/next/sidecar.js +56 -7
  13. package/dist/hosts/next/wrapper.d.ts +2 -0
  14. package/dist/hosts/next/wrapper.js +32 -3
  15. package/dist/hosts/projectBridge.d.ts +21 -0
  16. package/dist/hosts/projectBridge.js +98 -0
  17. package/dist/hosts/static/cli.d.ts +1 -1
  18. package/dist/hosts/static/cli.js +6 -1
  19. package/dist/hosts/static/manifest.d.ts +1 -1
  20. package/dist/hosts/static/manifest.js +3 -1
  21. package/dist/hosts/static/server.js +45 -3
  22. package/dist/hosts/vite/vite.d.ts +6 -1
  23. package/dist/hosts/vite/vite.js +82 -15
  24. package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
  25. package/dist/inspector/agent/McpConnectionDialog.js +20 -37
  26. package/dist/inspector/agent/client.d.ts +16 -3
  27. package/dist/inspector/agent/client.js +114 -11
  28. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  29. package/dist/inspector/agent/connectionStatus.js +11 -4
  30. package/dist/inspector/agent/httpTransport.d.ts +8 -0
  31. package/dist/inspector/agent/httpTransport.js +78 -18
  32. package/dist/inspector/agent/protocol.d.ts +11 -3
  33. package/dist/inspector/agent/protocol.js +1 -1
  34. package/dist/inspector/agent/verification.d.ts +1 -1
  35. package/dist/inspector/agent/verification.js +69 -12
  36. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  37. package/dist/inspector/canvas/CanvasCard.js +181 -91
  38. package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
  39. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  40. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  41. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  42. package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
  43. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  44. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  45. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  46. package/dist/inspector/canvas/canvasResize.js +52 -0
  47. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  48. package/dist/inspector/canvas/canvasStore.js +128 -61
  49. package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
  50. package/dist/inspector/canvas/frameProtocol.js +63 -2
  51. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
  52. package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
  53. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  54. package/dist/inspector/canvas/linkEligibility.js +0 -29
  55. package/dist/inspector/canvas/projection.d.ts +7 -0
  56. package/dist/inspector/canvas/projection.js +118 -25
  57. package/dist/inspector/canvas/rendererBootstrap.js +42 -29
  58. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  59. package/dist/inspector/canvas/rendererElementSelector.js +385 -58
  60. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  61. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  62. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  63. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  64. package/dist/inspector/canvas/sessionStore.js +12 -4
  65. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  66. package/dist/inspector/changes/changesLog.js +31 -1
  67. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  68. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  69. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  70. package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
  71. package/dist/inspector/changes/workspaceChanges.js +29 -3
  72. package/dist/inspector/clientManifest.d.ts +7 -0
  73. package/dist/inspector/clientManifest.js +12 -0
  74. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  75. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  76. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  77. package/dist/inspector/index.d.ts +2 -0
  78. package/dist/inspector/index.js +80 -32
  79. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  80. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  81. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  82. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  83. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  84. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  85. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  86. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  87. package/dist/inspector/inline-text/textEditChange.js +165 -0
  88. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  89. package/dist/inspector/inspection/selectionProperty.js +1 -0
  90. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  91. package/dist/inspector/overlay/DomNavigator.js +40 -0
  92. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  93. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  94. package/dist/inspector/overlay/clickPolicy.js +34 -3
  95. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  96. package/dist/inspector/overlay/elementSelector.js +18 -7
  97. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  98. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  99. package/dist/inspector/overlay/spacingGestures.js +351 -0
  100. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  101. package/dist/inspector/overlay/structuralGestures.js +12 -10
  102. package/dist/inspector/projection/managedStylesheet.js +4 -1
  103. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  104. package/dist/inspector/projection/renderedInstance.js +5 -1
  105. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  106. package/dist/inspector/projection/structuralProjection.js +85 -1
  107. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  108. package/dist/inspector/projection/textProjection.d.ts +2 -0
  109. package/dist/inspector/projection/textProjection.js +10 -4
  110. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  111. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  112. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  113. package/dist/inspector/prompt/generatePrompt.js +19 -2
  114. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  115. package/dist/inspector/runtime/editorShell.js +4 -0
  116. package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
  117. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  118. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  119. package/dist/inspector/selection/hierarchy.js +8 -0
  120. package/dist/inspector/selection/selectionStore.js +2 -0
  121. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  122. package/dist/inspector/settings/SettingsDialog.js +12 -2
  123. package/dist/inspector/shell/AppShell.js +5 -1
  124. package/dist/inspector/shell/ChangesLog.js +26 -22
  125. package/dist/inspector/shell/CopyPromptButton.js +102 -19
  126. package/dist/inspector/shell/EmptyState.js +1 -1
  127. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  128. package/dist/inspector/shell/InspectorShell.js +38 -42
  129. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  130. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  131. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  132. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  133. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  134. package/dist/inspector/shell/originalPreview.js +61 -0
  135. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  136. package/dist/inspector/shell/shortcuts.js +15 -0
  137. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  138. package/dist/inspector/sketch/SketchChanges.js +72 -0
  139. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  140. package/dist/inspector/sketch/SketchEditor.js +402 -0
  141. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  142. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  143. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  144. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  145. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  146. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  147. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  148. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  149. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  150. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  151. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  152. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  153. package/dist/inspector/sketch/capture.d.ts +55 -0
  154. package/dist/inspector/sketch/capture.js +350 -0
  155. package/dist/inspector/sketch/freehand.d.ts +13 -0
  156. package/dist/inspector/sketch/freehand.js +64 -0
  157. package/dist/inspector/sketch/geometry.d.ts +17 -0
  158. package/dist/inspector/sketch/geometry.js +98 -0
  159. package/dist/inspector/sketch/handoff.d.ts +21 -0
  160. package/dist/inspector/sketch/handoff.js +72 -0
  161. package/dist/inspector/sketch/interaction.d.ts +21 -0
  162. package/dist/inspector/sketch/interaction.js +98 -0
  163. package/dist/inspector/sketch/model.d.ts +104 -0
  164. package/dist/inspector/sketch/model.js +282 -0
  165. package/dist/inspector/sketch/persistence.d.ts +21 -0
  166. package/dist/inspector/sketch/persistence.js +205 -0
  167. package/dist/inspector/sketch/raster.d.ts +22 -0
  168. package/dist/inspector/sketch/raster.js +203 -0
  169. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  170. package/dist/inspector/sketch/sketchNote.js +30 -0
  171. package/dist/inspector/sketch/store.d.ts +47 -0
  172. package/dist/inspector/sketch/store.js +434 -0
  173. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  174. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  175. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  176. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  177. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  178. package/dist/inspector/styleEditors/GapField.js +2 -2
  179. package/dist/inspector/styleEditors/GridSection.js +1 -1
  180. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  181. package/dist/inspector/styleEditors/SpacingBox.js +31 -33
  182. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  183. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  184. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  185. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  186. package/dist/inspector/styleEditors/nudgeValue.js +93 -5
  187. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  188. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  189. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  190. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  191. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  192. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  193. package/dist/inspector/tokens/TokenField.js +130 -7
  194. package/dist/inspector/tokens/TokensPanel.js +2 -1
  195. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  196. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  197. package/dist/inspector/tokens/editActions.d.ts +2 -0
  198. package/dist/inspector/tokens/editActions.js +2 -2
  199. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  200. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  201. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  202. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  203. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  204. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  205. package/dist/inspector/tokens/resolution.d.ts +3 -26
  206. package/dist/inspector/tokens/resolution.js +44 -735
  207. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  208. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  209. package/dist/inspector/ui/Button.d.ts +1 -1
  210. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  211. package/dist/inspector/ui/Disclosure.js +13 -0
  212. package/dist/inspector/ui/SideValuesField.js +2 -2
  213. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  214. package/dist/inspector/ui/StatusCallout.js +2 -2
  215. package/dist/inspector/ui/dragCursor.d.ts +8 -0
  216. package/dist/inspector/ui/dragCursor.js +21 -0
  217. package/dist/inspector/ui/styles.js +21 -17
  218. package/dist/internal/agent-protocol.d.ts +65 -1
  219. package/dist/internal/agent-protocol.js +183 -3
  220. package/dist/nudge-ui.mjs +276 -58
  221. package/dist/transport/editor.d.ts +47 -0
  222. package/dist/transport/editor.js +163 -0
  223. package/dist/transport/index.d.ts +1 -0
  224. package/dist/transport/index.js +1 -0
  225. package/dist/transport/routes.d.ts +10 -0
  226. package/dist/transport/routes.js +10 -0
  227. package/package.json +3 -1
  228. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  229. package/dist/inspector/shell/DomNavigation.js +0 -30
  230. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  231. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -0,0 +1,402 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { Dialog } from "@base-ui/react/dialog";
4
+ import { IconArrowsMove, IconCheck, IconMessageCirclePlus, IconPencil, IconRectangle, IconX, } from "@tabler/icons-react";
5
+ import { Button } from "../ui/Button.js";
6
+ import { IconButton } from "../ui/IconButton.js";
7
+ import { StatusCallout } from "../ui/StatusCallout.js";
8
+ import { renderAnnotatedPng } from "./raster.js";
9
+ import { SketchSvgLayer } from "./freehand.js";
10
+ import { findSketchElementAt, translateSketchAnnotation, translateSketchStroke, } from "./geometry.js";
11
+ import { SKETCH_LIMITS, SKETCH_ANNOTATION_RADIUS, SKETCH_STROKE_COLOR, SKETCH_STROKE_OUTLINE, SKETCH_STROKE_WIDTH, } from "./model.js";
12
+ function portalContainer() {
13
+ return typeof document !== "undefined"
14
+ ? document.getElementById("nudge-ui-root")?.shadowRoot ?? document.body
15
+ : null;
16
+ }
17
+ function objectUrl(blob) {
18
+ if (!blob || typeof URL === "undefined" || typeof URL.createObjectURL !== "function")
19
+ return null;
20
+ return URL.createObjectURL(blob);
21
+ }
22
+ function clamp(value, maximum) {
23
+ return Math.max(0, Math.min(maximum, value));
24
+ }
25
+ function pointFromEvent(event, svg, width, height) {
26
+ const bounds = svg.getBoundingClientRect();
27
+ return {
28
+ x: clamp(((event.clientX - bounds.left) / Math.max(1, bounds.width)) * width, width),
29
+ y: clamp(((event.clientY - bounds.top) / Math.max(1, bounds.height)) * height, height),
30
+ };
31
+ }
32
+ function distance(left, right) {
33
+ return Math.hypot(left.x - right.x, left.y - right.y);
34
+ }
35
+ function rectanglePoints(start, end) {
36
+ const left = Math.min(start.x, end.x);
37
+ const right = Math.max(start.x, end.x);
38
+ const top = Math.min(start.y, end.y);
39
+ const bottom = Math.max(start.y, end.y);
40
+ return [
41
+ { x: left, y: top },
42
+ { x: right, y: top },
43
+ { x: right, y: bottom },
44
+ { x: left, y: bottom },
45
+ { x: left, y: top },
46
+ ];
47
+ }
48
+ export function SketchEditor({ open, document: sketchDocument, captured, onCancel, onSave }) {
49
+ const image = captured?.originalImage ?? sketchDocument?.originalImage ?? null;
50
+ const imageWidth = captured?.imageWidth ?? sketchDocument?.imageWidth ?? 0;
51
+ const imageHeight = captured?.imageHeight ?? sketchDocument?.imageHeight ?? 0;
52
+ const capture = captured?.capture ?? sketchDocument?.capture ?? null;
53
+ const [description, setDescription] = useState("");
54
+ const [tool, setTool] = useState("pen");
55
+ const [strokes, setStrokes] = useState([]);
56
+ const [redoStrokes, setRedoStrokes] = useState([]);
57
+ const [annotations, setAnnotations] = useState([]);
58
+ const [activeAnnotationId, setActiveAnnotationId] = useState(null);
59
+ const [activeAnnotationOriginal, setActiveAnnotationOriginal] = useState(null);
60
+ const [annotationDraft, setAnnotationDraft] = useState("");
61
+ const [draftPoints, setDraftPoints] = useState([]);
62
+ const [saving, setSaving] = useState(false);
63
+ const [error, setError] = useState(null);
64
+ const imageUrl = useMemo(() => objectUrl(image), [image]);
65
+ const svgRef = useRef(null);
66
+ const annotationInputRef = useRef(null);
67
+ const pointerId = useRef(null);
68
+ const moveState = useRef(null);
69
+ const draftPointLimitReached = useRef(false);
70
+ const activeAnnotation = annotations.find((annotation) => annotation.id === activeAnnotationId) ?? null;
71
+ useEffect(() => {
72
+ if (!open)
73
+ return;
74
+ setDescription(sketchDocument?.description ?? "");
75
+ setTool("pen");
76
+ setStrokes(sketchDocument ? sketchDocument.strokes.map((stroke) => ({ ...stroke, points: stroke.points.map((point) => ({ ...point })) })) : []);
77
+ setRedoStrokes([]);
78
+ setAnnotations(sketchDocument?.annotations?.map((annotation) => ({
79
+ ...annotation,
80
+ point: { ...annotation.point },
81
+ })) ?? []);
82
+ setActiveAnnotationId(null);
83
+ setActiveAnnotationOriginal(null);
84
+ setAnnotationDraft("");
85
+ setDraftPoints([]);
86
+ moveState.current = null;
87
+ setError(null);
88
+ setSaving(false);
89
+ }, [open, sketchDocument?.id, sketchDocument?.revision]);
90
+ useEffect(() => {
91
+ if (activeAnnotationId !== null)
92
+ annotationInputRef.current?.focus();
93
+ }, [activeAnnotationId]);
94
+ useEffect(() => {
95
+ if (open)
96
+ svgRef.current?.focus();
97
+ }, [open]);
98
+ useEffect(() => {
99
+ return () => {
100
+ if (imageUrl && typeof URL !== "undefined")
101
+ URL.revokeObjectURL(imageUrl);
102
+ };
103
+ }, [imageUrl]);
104
+ function openAnnotation(annotation) {
105
+ setActiveAnnotationId(annotation.id);
106
+ setActiveAnnotationOriginal(annotation.description);
107
+ setAnnotationDraft(annotation.description);
108
+ setError(null);
109
+ }
110
+ function annotationAt(point) {
111
+ return [...annotations].reverse().find((annotation) => distance(annotation.point, point) <= SKETCH_ANNOTATION_RADIUS * 1.5) ?? null;
112
+ }
113
+ function commitActiveAnnotation() {
114
+ if (activeAnnotationId === null)
115
+ return annotations;
116
+ const nextDescription = annotationDraft.trim();
117
+ if (!nextDescription) {
118
+ setError("Add a short description for this annotation before continuing.");
119
+ annotationInputRef.current?.focus();
120
+ return null;
121
+ }
122
+ const next = annotations.map((annotation) => annotation.id === activeAnnotationId
123
+ ? { ...annotation, description: nextDescription }
124
+ : annotation);
125
+ setAnnotations(next);
126
+ setActiveAnnotationId(null);
127
+ setActiveAnnotationOriginal(null);
128
+ setAnnotationDraft("");
129
+ setError(null);
130
+ return next;
131
+ }
132
+ function cancelActiveAnnotation() {
133
+ if (activeAnnotationId !== null && activeAnnotationOriginal === "") {
134
+ setAnnotations((current) => current.filter((annotation) => annotation.id !== activeAnnotationId));
135
+ }
136
+ setActiveAnnotationId(null);
137
+ setActiveAnnotationOriginal(null);
138
+ setAnnotationDraft("");
139
+ setError(null);
140
+ }
141
+ function selectTool(nextTool) {
142
+ if (activeAnnotationId !== null && !commitActiveAnnotation())
143
+ return;
144
+ setTool(nextTool);
145
+ setError(null);
146
+ }
147
+ function annotationEditorStyle(annotation) {
148
+ const bounds = svgRef.current?.getBoundingClientRect();
149
+ const scaleX = (bounds?.width ?? imageWidth) / Math.max(1, imageWidth);
150
+ const scaleY = (bounds?.height ?? imageHeight) / Math.max(1, imageHeight);
151
+ const width = 280;
152
+ const height = 52;
153
+ const availableWidth = bounds?.width ?? imageWidth;
154
+ const availableHeight = bounds?.height ?? imageHeight;
155
+ return {
156
+ left: clamp(annotation.point.x * scaleX + SKETCH_ANNOTATION_RADIUS * scaleX + 8, Math.max(8, availableWidth - width - 8)),
157
+ top: clamp(annotation.point.y * scaleY + SKETCH_ANNOTATION_RADIUS * scaleY + 8, Math.max(8, availableHeight - height - 8)),
158
+ };
159
+ }
160
+ function onPointerDown(event) {
161
+ if (saving || !imageWidth || !imageHeight || pointerId.current !== null || moveState.current !== null)
162
+ return;
163
+ const point = pointFromEvent(event, event.currentTarget, imageWidth, imageHeight);
164
+ if (tool === "move") {
165
+ event.preventDefault();
166
+ const element = findSketchElementAt(point, strokes, annotations);
167
+ if (!element)
168
+ return;
169
+ moveState.current = { pointerId: event.pointerId, element, lastPoint: point };
170
+ event.currentTarget.setPointerCapture(event.pointerId);
171
+ return;
172
+ }
173
+ if (tool === "annotate") {
174
+ event.preventDefault();
175
+ if (activeAnnotationId !== null) {
176
+ if (commitActiveAnnotation())
177
+ setError("Select Annotate again, then click another area.");
178
+ return;
179
+ }
180
+ const existing = annotationAt(point);
181
+ if (existing) {
182
+ openAnnotation(existing);
183
+ return;
184
+ }
185
+ if (annotations.length >= SKETCH_LIMITS.annotations) {
186
+ setError(`A sketch can contain at most ${SKETCH_LIMITS.annotations} annotations.`);
187
+ return;
188
+ }
189
+ const usedNumbers = new Set(annotations.map((annotation) => annotation.number));
190
+ let nextNumber = 1;
191
+ while (usedNumbers.has(nextNumber))
192
+ nextNumber += 1;
193
+ const annotation = {
194
+ id: `annotation-${Date.now()}-${Math.random().toString(36).slice(2)}`,
195
+ number: nextNumber,
196
+ point,
197
+ description: "",
198
+ };
199
+ setAnnotations((current) => [...current, annotation]);
200
+ setActiveAnnotationId(annotation.id);
201
+ setActiveAnnotationOriginal("");
202
+ setAnnotationDraft("");
203
+ setError(null);
204
+ return;
205
+ }
206
+ event.preventDefault();
207
+ pointerId.current = event.pointerId;
208
+ draftPointLimitReached.current = false;
209
+ event.currentTarget.setPointerCapture(event.pointerId);
210
+ setDraftPoints([point]);
211
+ }
212
+ function onPointerMove(event) {
213
+ const activeMove = moveState.current;
214
+ if (activeMove?.pointerId === event.pointerId) {
215
+ event.preventDefault();
216
+ const point = pointFromEvent(event, event.currentTarget, imageWidth, imageHeight);
217
+ const delta = {
218
+ x: point.x - activeMove.lastPoint.x,
219
+ y: point.y - activeMove.lastPoint.y,
220
+ };
221
+ if (delta.x !== 0 || delta.y !== 0) {
222
+ const bounds = { width: imageWidth, height: imageHeight };
223
+ if (activeMove.element.kind === "stroke") {
224
+ setStrokes((current) => current.map((stroke) => stroke.id === activeMove.element.id
225
+ ? translateSketchStroke(stroke, delta, bounds)
226
+ : stroke));
227
+ }
228
+ else {
229
+ setAnnotations((current) => current.map((annotation) => annotation.id === activeMove.element.id
230
+ ? translateSketchAnnotation(annotation, delta, bounds)
231
+ : annotation));
232
+ }
233
+ activeMove.lastPoint = point;
234
+ }
235
+ return;
236
+ }
237
+ if (pointerId.current !== event.pointerId)
238
+ return;
239
+ event.preventDefault();
240
+ const point = pointFromEvent(event, event.currentTarget, imageWidth, imageHeight);
241
+ setDraftPoints((previous) => {
242
+ const last = previous.at(-1);
243
+ if (last && distance(last, point) < 1)
244
+ return previous;
245
+ if (previous.length >= SKETCH_LIMITS.points) {
246
+ draftPointLimitReached.current = true;
247
+ return previous;
248
+ }
249
+ return [...previous, point];
250
+ });
251
+ }
252
+ function finishPointer(event) {
253
+ const activeMove = moveState.current;
254
+ if (activeMove?.pointerId === event.pointerId) {
255
+ moveState.current = null;
256
+ try {
257
+ event.currentTarget.releasePointerCapture(event.pointerId);
258
+ }
259
+ catch {
260
+ // The pointer may already have been released by the browser.
261
+ }
262
+ return;
263
+ }
264
+ if (pointerId.current !== event.pointerId)
265
+ return;
266
+ pointerId.current = null;
267
+ try {
268
+ event.currentTarget.releasePointerCapture(event.pointerId);
269
+ }
270
+ catch {
271
+ // The pointer may already have been released by the browser.
272
+ }
273
+ if (draftPointLimitReached.current) {
274
+ draftPointLimitReached.current = false;
275
+ setDraftPoints([]);
276
+ setError(`A stroke can contain at most ${SKETCH_LIMITS.points.toLocaleString()} points. The incomplete stroke was discarded.`);
277
+ return;
278
+ }
279
+ setDraftPoints((previous) => {
280
+ if (previous.length === 0)
281
+ return previous;
282
+ const points = tool === "rectangle" && previous.length > 1
283
+ ? rectanglePoints(previous[0], previous.at(-1))
284
+ : previous;
285
+ const totalPoints = strokes.reduce((total, stroke) => total + stroke.points.length, 0) + points.length;
286
+ if (strokes.length >= SKETCH_LIMITS.strokes || totalPoints > SKETCH_LIMITS.points) {
287
+ setError("The sketch has reached its stroke limit. Undo a mark before drawing again.");
288
+ return [];
289
+ }
290
+ if (points.length < 2)
291
+ return [];
292
+ if (tool === "rectangle" && distance(previous[0], previous.at(-1)) < 1)
293
+ return [];
294
+ const stroke = {
295
+ id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2)}`,
296
+ kind: tool === "rectangle" ? "rectangle" : "freehand",
297
+ width: SKETCH_STROKE_WIDTH,
298
+ color: SKETCH_STROKE_COLOR,
299
+ outlineColor: SKETCH_STROKE_OUTLINE,
300
+ points: points.map((point) => ({ ...point })),
301
+ };
302
+ setStrokes((current) => [...current, stroke]);
303
+ setRedoStrokes([]);
304
+ return [];
305
+ });
306
+ }
307
+ function undo() {
308
+ setStrokes((current) => {
309
+ const last = current.at(-1);
310
+ if (!last)
311
+ return current;
312
+ setRedoStrokes((redo) => [...redo, last]);
313
+ return current.slice(0, -1);
314
+ });
315
+ }
316
+ function redo() {
317
+ setRedoStrokes((current) => {
318
+ const last = current.at(-1);
319
+ if (!last)
320
+ return current;
321
+ setStrokes((existing) => [...existing, last]);
322
+ return current.slice(0, -1);
323
+ });
324
+ }
325
+ function onEditorKeyDown(event) {
326
+ const mod = event.metaKey || event.ctrlKey;
327
+ if (mod && event.key.toLowerCase() === "z") {
328
+ event.preventDefault();
329
+ event.stopPropagation();
330
+ if (event.shiftKey)
331
+ redo();
332
+ else
333
+ undo();
334
+ }
335
+ else if (mod && event.key.toLowerCase() === "y") {
336
+ event.preventDefault();
337
+ event.stopPropagation();
338
+ redo();
339
+ }
340
+ }
341
+ function onAnnotationKeyDown(event) {
342
+ if (event.key === "Enter") {
343
+ event.preventDefault();
344
+ commitActiveAnnotation();
345
+ }
346
+ else if (event.key === "Escape") {
347
+ event.preventDefault();
348
+ cancelActiveAnnotation();
349
+ }
350
+ }
351
+ async function save() {
352
+ if (!image || !capture || saving)
353
+ return;
354
+ const committedAnnotations = commitActiveAnnotation();
355
+ if (committedAnnotations === null)
356
+ return;
357
+ if (strokes.length === 0 && committedAnnotations.length === 0 && description.trim() === "") {
358
+ setError("Add a mark or a short description before saving the sketch.");
359
+ return;
360
+ }
361
+ setSaving(true);
362
+ setError(null);
363
+ try {
364
+ const rendered = await renderAnnotatedPng(image, imageWidth, imageHeight, strokes, committedAnnotations);
365
+ await onSave({
366
+ ...(sketchDocument ? { id: sketchDocument.id } : {}),
367
+ capture: {
368
+ ...capture,
369
+ imageWidth: rendered.width,
370
+ imageHeight: rendered.height,
371
+ },
372
+ description: description.trim(),
373
+ strokes,
374
+ annotations: committedAnnotations,
375
+ imageWidth: rendered.width,
376
+ imageHeight: rendered.height,
377
+ originalImage: image,
378
+ annotatedImage: rendered.blob,
379
+ });
380
+ }
381
+ catch (saveError) {
382
+ setError(saveError instanceof Error ? saveError.message : "The sketch could not be saved.");
383
+ setSaving(false);
384
+ }
385
+ }
386
+ const draftStroke = draftPoints.length > 0
387
+ ? {
388
+ id: "draft",
389
+ kind: tool === "rectangle" ? "rectangle" : "freehand",
390
+ width: SKETCH_STROKE_WIDTH,
391
+ color: SKETCH_STROKE_COLOR,
392
+ outlineColor: SKETCH_STROKE_OUTLINE,
393
+ points: tool === "rectangle" && draftPoints.length > 1
394
+ ? rectanglePoints(draftPoints[0], draftPoints.at(-1))
395
+ : draftPoints.map((point) => ({ ...point })),
396
+ }
397
+ : null;
398
+ if (!open || !image || !capture)
399
+ return null;
400
+ return (_jsx(Dialog.Root, { open: open, modal: false, disablePointerDismissal: true, onOpenChange: (nextOpen) => { if (!nextOpen && !saving)
401
+ onCancel(); }, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "sketch__backdrop sketch__editor-backdrop", "data-test": "sketch-editor-backdrop" }), _jsxs(Dialog.Popup, { className: "sketch__popup", "data-test": "sketch-editor", initialFocus: false, onKeyDown: onEditorKeyDown, children: [_jsx(Dialog.Title, { className: "sketch__title sketch__sr-only", children: "Annotate viewport" }), _jsx(Dialog.Description, { className: "sketch__description sketch__sr-only", children: "Draw directly over the captured UI. Select Annotate to label an area with a numbered note." }), _jsx("div", { className: "sketch__canvas-frame", children: _jsxs("div", { className: "sketch__image-wrap", children: [imageUrl ? _jsx("img", { className: "sketch__image", src: imageUrl, alt: "Captured viewport", draggable: false }) : null, _jsx("svg", { ref: svgRef, className: `sketch__canvas sketch__canvas--${tool}`, width: imageWidth, height: imageHeight, viewBox: `0 0 ${imageWidth} ${imageHeight}`, preserveAspectRatio: "none", pointerEvents: "all", tabIndex: 0, "aria-label": "Sketch over the captured viewport", onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: finishPointer, onPointerCancel: finishPointer, children: _jsx(SketchSvgLayer, { strokes: draftStroke ? [...strokes, draftStroke] : strokes, annotations: annotations }) }), activeAnnotation ? (_jsxs("div", { className: "sketch__annotation-editor", role: "dialog", "aria-label": `Annotation ${activeAnnotation.number}`, style: annotationEditorStyle(activeAnnotation), children: [_jsxs("label", { className: "sketch__sr-only", htmlFor: "sketch-editor-annotation-description", children: ["Annotation ", activeAnnotation.number, " description"] }), _jsx("input", { ref: annotationInputRef, id: "sketch-editor-annotation-description", value: annotationDraft, maxLength: SKETCH_LIMITS.description, placeholder: "Describe this area\u2026", autoComplete: "off", "data-test": "sketch-editor-annotation-description", onChange: (event) => setAnnotationDraft(event.target.value), onKeyDown: onAnnotationKeyDown }), _jsx(IconButton, { label: "Cancel annotation", variant: "quiet", size: "compact", "data-test": "sketch-editor-annotation-cancel", onClick: cancelActiveAnnotation, children: _jsx(IconX, { size: "var(--icon-size-small)", "aria-hidden": "true" }) }), _jsxs(Button, { variant: "primary", size: "compact", type: "button", "data-test": "sketch-editor-annotation-save", onClick: () => { commitActiveAnnotation(); }, children: [_jsx(IconCheck, { size: "var(--icon-size-small)", "aria-hidden": "true" }), "Add"] })] })) : null] }) }), _jsxs("div", { className: "sketch__floating-panel", children: [_jsxs("div", { className: "sketch__toolbar", role: "toolbar", "aria-label": "Sketch tools", children: [_jsx(IconButton, { label: "Move", title: "Move", variant: "quiet", "aria-pressed": tool === "move", "data-active": tool === "move", "data-test": "sketch-move", onClick: () => selectTool("move"), disabled: saving, children: _jsx(IconArrowsMove, { size: "var(--icon-size-small)", "aria-hidden": "true" }) }), _jsx(IconButton, { label: "Pen", title: "Pen", variant: "quiet", "aria-pressed": tool === "pen", "data-active": tool === "pen", "data-test": "sketch-pen", onClick: () => selectTool("pen"), disabled: saving, children: _jsx(IconPencil, { size: "var(--icon-size-small)", "aria-hidden": "true" }) }), _jsx(IconButton, { label: "Rectangle", title: "Rectangle", variant: "quiet", "aria-pressed": tool === "rectangle", "data-active": tool === "rectangle", "data-test": "sketch-rectangle", onClick: () => selectTool("rectangle"), disabled: saving, children: _jsx(IconRectangle, { size: "var(--icon-size-small)", "aria-hidden": "true" }) }), _jsx(IconButton, { label: "Annotate", title: "Annotate", variant: "quiet", "aria-pressed": tool === "annotate", "data-active": tool === "annotate", "data-test": "sketch-annotate", onClick: () => selectTool("annotate"), disabled: saving, children: _jsx(IconMessageCirclePlus, { size: "var(--icon-size-small)", "aria-hidden": "true" }) }), _jsx("span", { className: "sketch__toolbar-divider", "aria-hidden": "true" }), _jsx(Button, { variant: "quiet", type: "button", "data-test": "sketch-cancel", onClick: onCancel, disabled: saving, children: "Cancel" }), _jsxs(Button, { variant: "primary", type: "button", "data-test": "sketch-done", onClick: () => void save(), disabled: saving || (strokes.length === 0 && annotations.length === 0 && description.trim() === ""), children: [_jsx(IconCheck, { size: "var(--icon-size-small)", "aria-hidden": "true" }), saving ? "Saving…" : "Done"] })] }), error ? _jsx(StatusCallout, { tone: "danger", "data-test": "sketch-editor-error", children: error }) : null] })] })] }) }));
402
+ }
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function SketchEntryButton({ hostElement }: {
3
+ readonly hostElement: HTMLElement | null;
4
+ }): ReactElement;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconPencil } from "@tabler/icons-react";
3
+ import { Button } from "../ui/Button.js";
4
+ import { isInlineTextEditingActive } from "../inline-text/inlineTextEditor.js";
5
+ import { useSketchInteractionActive } from "./interaction.js";
6
+ import { startSketchCapture } from "./SketchWorkspace.js";
7
+ export function SketchEntryButton({ hostElement }) {
8
+ const active = useSketchInteractionActive();
9
+ const disabled = active || isInlineTextEditingActive();
10
+ return (_jsxs(Button, { variant: "quiet", size: "compact", className: "panel__sketch-button", type: "button", "data-test": "sketch-entry", "aria-label": "Sketch viewport", title: "Sketch viewport", disabled: disabled || !hostElement, onClick: () => startSketchCapture(hostElement), children: [_jsx(IconPencil, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), _jsx("span", { className: "panel__sketch-label", children: "Sketch" })] }));
11
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactElement } from "react";
2
+ import type { SketchDocument, SketchPoint } from "./model.js";
3
+ interface SketchFrameOverlayProps {
4
+ readonly iframe: HTMLIFrameElement | null;
5
+ readonly cardUrl: string;
6
+ readonly ready: boolean;
7
+ }
8
+ export declare function sketchRouteKey(url: string): string | null;
9
+ export declare function projectSketchPoint(point: SketchPoint, document: SketchDocument, frameWindow: Window, width: number, height: number): SketchPoint;
10
+ export declare function SketchFrameOverlay({ iframe, cardUrl, ready }: SketchFrameOverlayProps): ReactElement | null;
11
+ export {};
@@ -0,0 +1,119 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { SketchSvgLayer } from "./freehand.js";
4
+ import { useSketchStore } from "./store.js";
5
+ import { getSketchScrollPosition } from "./capture.js";
6
+ export function sketchRouteKey(url) {
7
+ try {
8
+ const parsed = new URL(url);
9
+ // These parameters belong to the inspector host, not to the application
10
+ // route. Removing them lets a sketch follow the same page in Focus mode,
11
+ // the canvas, and a direct editor launch.
12
+ parsed.searchParams.delete("__nudge_ui_direct");
13
+ parsed.searchParams.delete("nudge-ui");
14
+ // Hashes identify an in-document location, not a different sketch canvas.
15
+ // Keep saved marks attached when navigation changes only the anchor.
16
+ return `${parsed.origin}${parsed.pathname}${parsed.search}`;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ function frameRoute(iframe, fallback) {
23
+ try {
24
+ return sketchRouteKey(iframe.contentWindow?.location.href ?? fallback);
25
+ }
26
+ catch {
27
+ return sketchRouteKey(fallback);
28
+ }
29
+ }
30
+ function sameFrame(document, iframe, cardUrl) {
31
+ const sketchRoute = sketchRouteKey(document.capture.url);
32
+ if (!sketchRoute)
33
+ return false;
34
+ return sketchRoute === frameRoute(iframe, cardUrl);
35
+ }
36
+ export function projectSketchPoint(point, document, frameWindow, width, height) {
37
+ const capture = document.capture;
38
+ const imageWidth = Math.max(1, document.imageWidth);
39
+ const imageHeight = Math.max(1, document.imageHeight);
40
+ const viewportWidth = Math.max(1, frameWindow.innerWidth || width);
41
+ const viewportHeight = Math.max(1, frameWindow.innerHeight || height);
42
+ const scroll = getSketchScrollPosition(frameWindow);
43
+ // Preserve the point's relative horizontal position when the responsive
44
+ // iframe changes width. Apply the scroll delta separately so horizontal
45
+ // page scrolling still moves the sketch with the content.
46
+ const captureLocalX = (point.x / imageWidth) * capture.viewportWidth;
47
+ const currentLocalX = (captureLocalX / Math.max(1, capture.viewportWidth)) * viewportWidth
48
+ - (scroll.x - capture.scrollX);
49
+ const pageY = capture.scrollY + (point.y / imageHeight) * capture.viewportHeight;
50
+ return {
51
+ x: (currentLocalX / viewportWidth) * width,
52
+ y: ((pageY - scroll.y) / viewportHeight) * height,
53
+ };
54
+ }
55
+ function visibleStroke(stroke, document, frameWindow, width, height) {
56
+ const captureScale = document.capture.viewportWidth / Math.max(1, document.imageWidth);
57
+ const currentScale = width / Math.max(1, frameWindow.innerWidth || width);
58
+ return {
59
+ ...stroke,
60
+ width: stroke.width * captureScale * currentScale,
61
+ points: stroke.points.map((point) => projectSketchPoint(point, document, frameWindow, width, height)),
62
+ };
63
+ }
64
+ export function SketchFrameOverlay({ iframe, cardUrl, ready }) {
65
+ const { items } = useSketchStore();
66
+ const [revision, setRevision] = useState(0);
67
+ const documents = useMemo(() => ready && iframe
68
+ ? items.map((item) => item.document).filter((document) => sameFrame(document, iframe, cardUrl))
69
+ : [], [cardUrl, iframe, items, ready]);
70
+ useEffect(() => {
71
+ if (!iframe || documents.length === 0)
72
+ return;
73
+ const frameWindow = iframe.contentWindow;
74
+ if (!frameWindow)
75
+ return;
76
+ const refresh = () => setRevision((value) => value + 1);
77
+ window.addEventListener("resize", refresh);
78
+ frameWindow.addEventListener("resize", refresh);
79
+ frameWindow.addEventListener("scroll", refresh, true);
80
+ const observer = typeof ResizeObserver === "undefined"
81
+ ? null
82
+ : new ResizeObserver(refresh);
83
+ observer?.observe(iframe);
84
+ return () => {
85
+ window.removeEventListener("resize", refresh);
86
+ frameWindow.removeEventListener("resize", refresh);
87
+ frameWindow.removeEventListener("scroll", refresh, true);
88
+ observer?.disconnect();
89
+ };
90
+ }, [documents.length, iframe]);
91
+ const width = iframe
92
+ ? Math.max(1, iframe.clientWidth || iframe.getBoundingClientRect().width)
93
+ : 1;
94
+ const height = iframe
95
+ ? Math.max(1, iframe.clientHeight || iframe.getBoundingClientRect().height)
96
+ : 1;
97
+ const frameWindow = iframe?.contentWindow ?? null;
98
+ const layers = useMemo(() => {
99
+ if (!iframe || !frameWindow || documents.length === 0) {
100
+ return {
101
+ strokes: [],
102
+ annotations: [],
103
+ };
104
+ }
105
+ const strokes = [];
106
+ const annotations = [];
107
+ for (const document of documents) {
108
+ strokes.push(...document.strokes.map((stroke) => visibleStroke(stroke, document, frameWindow, width, height)));
109
+ annotations.push(...(document.annotations ?? []).map((annotation) => ({
110
+ ...annotation,
111
+ point: projectSketchPoint(annotation.point, document, frameWindow, width, height),
112
+ })));
113
+ }
114
+ return { strokes, annotations };
115
+ }, [documents, frameWindow, height, iframe, revision, width]);
116
+ if (!iframe || documents.length === 0 || !frameWindow)
117
+ return null;
118
+ return (_jsx("svg", { className: "canvas-card__sketch-overlay", width: width, height: height, viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "none", "aria-hidden": "true", "data-test": "canvas-sketch-overlay", children: _jsx(SketchSvgLayer, { strokes: layers.strokes, annotations: layers.annotations }) }));
119
+ }
@@ -0,0 +1,2 @@
1
+ import type { ReactElement } from "react";
2
+ export declare function SketchLayersPanel(): ReactElement | null;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { IconCheck, IconCopy, IconX } from "@tabler/icons-react";
4
+ import { Button } from "../ui/Button.js";
5
+ import { IconButton } from "../ui/IconButton.js";
6
+ import { copyImageToClipboard } from "./raster.js";
7
+ import { closeSketchNote } from "./sketchNote.js";
8
+ import { removeSketch, useSketchStore } from "./store.js";
9
+ function SketchThumbnail({ item }) {
10
+ const [url, setUrl] = useState(null);
11
+ const hasMarks = item.document.strokes.length > 0 || (item.document.annotations?.length ?? 0) > 0;
12
+ useEffect(() => {
13
+ if (!hasMarks) {
14
+ setUrl(null);
15
+ return;
16
+ }
17
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function")
18
+ return;
19
+ const next = URL.createObjectURL(item.document.annotatedImage);
20
+ setUrl(next);
21
+ return () => URL.revokeObjectURL(next);
22
+ }, [hasMarks, item.document.annotatedImage]);
23
+ return hasMarks && url ? (_jsx("img", { className: "sketch-layers__thumbnail", src: url, alt: "", "aria-hidden": "true" })) : (_jsx("span", { className: "sketch-layers__thumbnail sketch-layers__thumbnail--empty", "aria-hidden": "true" }));
24
+ }
25
+ function SketchLayer({ item, index }) {
26
+ const [copied, setCopied] = useState(false);
27
+ const [error, setError] = useState(null);
28
+ const busy = item.status === "dispatching" || item.status === "handing-off";
29
+ async function copyImage() {
30
+ setError(null);
31
+ try {
32
+ await copyImageToClipboard(item.document.annotatedImage);
33
+ setCopied(true);
34
+ window.setTimeout(() => setCopied(false), 1500);
35
+ }
36
+ catch (copyError) {
37
+ setError(copyError instanceof Error ? copyError.message : "The sketch image could not be copied.");
38
+ }
39
+ }
40
+ async function deleteLayer() {
41
+ setError(null);
42
+ try {
43
+ if (!await removeSketch(item.document.id)) {
44
+ setError("This sketch is being sent and cannot be deleted yet.");
45
+ }
46
+ else {
47
+ closeSketchNote();
48
+ }
49
+ }
50
+ catch (deleteError) {
51
+ setError(deleteError instanceof Error ? deleteError.message : "The sketch could not be deleted.");
52
+ }
53
+ }
54
+ return (_jsxs("div", { className: "sketch-layers__item", "data-test": "sketch-layer", "data-sketch-id": item.document.id, children: [_jsxs(Button, { className: "sketch-layers__copy", variant: "secondary", type: "button", "data-test": "sketch-layer-copy-image", onClick: () => void copyImage(), disabled: busy, children: [_jsxs("span", { className: "sketch-layers__copy-label", children: [copied
55
+ ? _jsx(IconCheck, { size: "var(--icon-size-small)", "aria-hidden": "true" })
56
+ : _jsx(IconCopy, { size: "var(--icon-size-small)", "aria-hidden": "true" }), _jsx("span", { className: "sketch-layers__name", children: copied ? "Copied" : `Sketch ${index + 1}` })] }), _jsx(SketchThumbnail, { item: item })] }), _jsx(IconButton, { className: "sketch-layers__delete", variant: "quiet", size: "compact", label: "Delete sketch", title: "Delete sketch", "data-test": "sketch-layer-delete", onClick: () => void deleteLayer(), disabled: busy, children: _jsx(IconX, { size: "var(--icon-size-small)", "aria-hidden": "true" }) }), error ? _jsx("span", { className: "sketch-layers__error", role: "status", children: error }) : null] }));
57
+ }
58
+ export function SketchLayersPanel() {
59
+ const { items } = useSketchStore();
60
+ if (items.length === 0)
61
+ return null;
62
+ return (_jsxs("section", { className: "sketch-layers", "data-test": "sketch-layers", "aria-label": "Sketch layers", children: [items.map((item, index) => (_jsx(SketchLayer, { item: item, index: index }, `${item.document.id}:${item.document.revision}`))), _jsx("p", { className: "sketch-layers__hint", "data-test": "sketch-copy-helper", children: "Copy prompt and sketch individually into agent" })] }));
63
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactElement } from "react";
2
+ import { type SketchViewport } from "./capture.js";
3
+ import { type SketchAnnotation, type SketchStroke } from "./model.js";
4
+ import type { SketchEntryTool } from "./interaction.js";
5
+ export interface SketchLiveDraft {
6
+ /** Kept for compatibility with sketches created with the original editor. */
7
+ readonly description: string;
8
+ readonly strokes: readonly SketchStroke[];
9
+ readonly annotations: readonly SketchAnnotation[];
10
+ readonly viewport: SketchViewport;
11
+ }
12
+ export interface SketchOverlayProps {
13
+ readonly open: boolean;
14
+ readonly hostElement: HTMLElement;
15
+ readonly initialTool?: SketchEntryTool;
16
+ readonly onCancel: () => void;
17
+ readonly onDone: (draft: SketchLiveDraft) => Promise<void>;
18
+ }
19
+ export declare function SketchOverlay({ open, hostElement, initialTool, onCancel, onDone }: SketchOverlayProps): ReactElement | null;