nudge-ui 0.2.3 → 0.2.5

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 (264) hide show
  1. package/dist/cli/agentCommand.js +4 -28
  2. package/dist/client.mjs +870 -212
  3. package/dist/hosts/next/wrapper.js +1 -7
  4. package/dist/hosts/projectBridge.js +3 -17
  5. package/dist/inspector/agent/McpConnectionDialog.js +7 -3
  6. package/dist/inspector/agent/activity.d.ts +1 -1
  7. package/dist/inspector/agent/activity.js +3 -2
  8. package/dist/inspector/agent/client.d.ts +2 -2
  9. package/dist/inspector/agent/client.js +28 -29
  10. package/dist/inspector/agent/connectionStatus.d.ts +3 -1
  11. package/dist/inspector/agent/connectionStatus.js +25 -0
  12. package/dist/inspector/agent/httpTransport.js +31 -147
  13. package/dist/inspector/agent/protocol.d.ts +1 -1
  14. package/dist/inspector/agent/protocol.js +1 -1
  15. package/dist/inspector/agent/verification.d.ts +5 -7
  16. package/dist/inspector/agent/verification.js +67 -20
  17. package/dist/inspector/boot.d.ts +3 -0
  18. package/dist/inspector/boot.js +297 -0
  19. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +2 -1
  20. package/dist/inspector/canvas/AgentActivityOverlay.js +6 -3
  21. package/dist/inspector/canvas/CanvasCard.js +23 -12
  22. package/dist/inspector/canvas/CanvasElementOverlay.js +97 -128
  23. package/dist/inspector/canvas/CanvasToolbar.js +2 -1
  24. package/dist/inspector/canvas/CanvasWorkspace.js +119 -162
  25. package/dist/inspector/canvas/LockedWorkspaceNotice.d.ts +3 -2
  26. package/dist/inspector/canvas/LockedWorkspaceNotice.js +10 -6
  27. package/dist/inspector/canvas/PageCard.d.ts +10 -0
  28. package/dist/inspector/canvas/PageCard.js +51 -0
  29. package/dist/inspector/canvas/SketchToolOptions.d.ts +5 -0
  30. package/dist/inspector/canvas/SketchToolOptions.js +86 -0
  31. package/dist/inspector/canvas/StaleChangeIndicator.d.ts +1 -1
  32. package/dist/inspector/canvas/StaleChangeIndicator.js +1 -1
  33. package/dist/inspector/canvas/ToolOptionsDrawer.d.ts +21 -0
  34. package/dist/inspector/canvas/ToolOptionsDrawer.js +77 -0
  35. package/dist/inspector/canvas/activeCanvasDocument.d.ts +3 -2
  36. package/dist/inspector/canvas/assets/eraser.svg +28 -0
  37. package/dist/inspector/canvas/assets/pen.svg +25 -0
  38. package/dist/inspector/canvas/canvasFrame.d.ts +16 -0
  39. package/dist/inspector/canvas/canvasFrame.js +10 -0
  40. package/dist/inspector/canvas/canvasGestures.d.ts +0 -1
  41. package/dist/inspector/canvas/canvasGestures.js +0 -6
  42. package/dist/inspector/canvas/canvasStore.d.ts +6 -0
  43. package/dist/inspector/canvas/canvasStore.js +36 -1
  44. package/dist/inspector/canvas/frameConnection.d.ts +48 -0
  45. package/dist/inspector/canvas/frameConnection.js +73 -0
  46. package/dist/inspector/canvas/frameProtocol.d.ts +18 -1
  47. package/dist/inspector/canvas/frameProtocol.js +36 -0
  48. package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
  49. package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
  50. package/dist/inspector/canvas/pageSurface.d.ts +11 -0
  51. package/dist/inspector/canvas/pageSurface.js +34 -0
  52. package/dist/inspector/canvas/projection.d.ts +19 -9
  53. package/dist/inspector/canvas/projection.js +58 -31
  54. package/dist/inspector/canvas/rendererBootstrap.js +4 -1
  55. package/dist/inspector/canvas/rendererElementSelector.js +9 -1
  56. package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
  57. package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
  58. package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
  59. package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
  60. package/dist/inspector/canvas/rendererStylesheet.js +15 -0
  61. package/dist/inspector/canvas/sessionStore.d.ts +2 -2
  62. package/dist/inspector/canvas/sessionStore.js +12 -9
  63. package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
  64. package/dist/inspector/canvas/staleChangeDetector.js +10 -22
  65. package/dist/inspector/changes/codecs.d.ts +2 -0
  66. package/dist/inspector/changes/codecs.js +27 -3
  67. package/dist/inspector/changes/draftPreview.d.ts +12 -0
  68. package/dist/inspector/changes/draftPreview.js +133 -0
  69. package/dist/inspector/changes/editModel.d.ts +18 -0
  70. package/dist/inspector/changes/editModel.js +16 -3
  71. package/dist/inspector/changes/managedStyleProjection.js +2 -1
  72. package/dist/inspector/changes/previewDiagnostics.js +1 -1
  73. package/dist/inspector/changes/types.d.ts +18 -1
  74. package/dist/inspector/comments/CommentsOverlay.js +18 -16
  75. package/dist/inspector/comments/element.js +10 -3
  76. package/dist/inspector/comments/store.js +3 -2
  77. package/dist/inspector/comments/verification.js +1 -1
  78. package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
  79. package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
  80. package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
  81. package/dist/inspector/componentSemantics/changeAction.js +1 -1
  82. package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
  83. package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
  84. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
  85. package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
  86. package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
  87. package/dist/inspector/componentSemantics/textBinding.js +4 -45
  88. package/dist/inspector/componentSemantics/types.d.ts +11 -0
  89. package/dist/inspector/drafts/draftStore.d.ts +109 -0
  90. package/dist/inspector/drafts/draftStore.js +451 -0
  91. package/dist/inspector/drafts/model.d.ts +9 -1
  92. package/dist/inspector/drafts/model.js +5 -1
  93. package/dist/inspector/drafts/store.d.ts +53 -15
  94. package/dist/inspector/drafts/store.js +143 -169
  95. package/dist/inspector/index.d.ts +2 -4
  96. package/dist/inspector/index.js +2 -264
  97. package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
  98. package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
  99. package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
  100. package/dist/inspector/inspection/bridge.d.ts +1 -1
  101. package/dist/inspector/inspection/bridge.js +2 -1
  102. package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
  103. package/dist/inspector/inspection/browserCssInspection.js +28 -6
  104. package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
  105. package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
  106. package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
  107. package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
  108. package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
  109. package/dist/inspector/iterations/capture.d.ts +2 -1
  110. package/dist/inspector/iterations/capture.js +2 -1
  111. package/dist/inspector/iterations/client.d.ts +2 -1
  112. package/dist/inspector/iterations/coordinator.d.ts +4 -3
  113. package/dist/inspector/iterations/coordinator.js +5 -3
  114. package/dist/inspector/iterations/prompt.js +9 -8
  115. package/dist/inspector/overlay/interactionStyles.js +17 -0
  116. package/dist/inspector/pageInspector.d.ts +42 -0
  117. package/dist/inspector/pageInspector.js +342 -0
  118. package/dist/inspector/projection/draftProjection.d.ts +10 -2
  119. package/dist/inspector/projection/draftProjection.js +9 -1
  120. package/dist/inspector/projection/managedStylesheet.js +24 -4
  121. package/dist/inspector/projection/renderedInstance.d.ts +15 -2
  122. package/dist/inspector/projection/renderedInstance.js +60 -7
  123. package/dist/inspector/projection/structuralProjection.d.ts +0 -9
  124. package/dist/inspector/projection/structuralProjection.js +16 -46
  125. package/dist/inspector/projection/textProjection.d.ts +8 -2
  126. package/dist/inspector/projection/textProjection.js +77 -24
  127. package/dist/inspector/prompt/clipboardHandoff.d.ts +14 -0
  128. package/dist/inspector/prompt/clipboardHandoff.js +47 -16
  129. package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
  130. package/dist/inspector/prompt/generatePrompt.js +248 -148
  131. package/dist/inspector/prompt/promptDocument.d.ts +3 -0
  132. package/dist/inspector/prompt/promptDocument.js +8 -0
  133. package/dist/inspector/prompt/promptSettings.js +7 -5
  134. package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
  135. package/dist/inspector/runtime/inspectorStorage.js +42 -0
  136. package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
  137. package/dist/inspector/runtime/ownedStylesheets.js +72 -0
  138. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  139. package/dist/inspector/runtime/runtimeConfig.js +10 -1
  140. package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
  141. package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
  142. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
  143. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
  144. package/dist/inspector/selection/editScope.d.ts +1 -0
  145. package/dist/inspector/selection/editScope.js +13 -2
  146. package/dist/inspector/selection/sourceSite.d.ts +12 -0
  147. package/dist/inspector/selection/sourceSite.js +28 -0
  148. package/dist/inspector/shell/AgentStatusButton.js +2 -2
  149. package/dist/inspector/shell/AppShell.js +6 -2
  150. package/dist/inspector/shell/ChangesLog.js +27 -9
  151. package/dist/inspector/shell/CopyPromptButton.js +74 -26
  152. package/dist/inspector/shell/EmptyState.js +3 -3
  153. package/dist/inspector/shell/InspectorShell.d.ts +0 -3
  154. package/dist/inspector/shell/InspectorShell.js +21 -13
  155. package/dist/inspector/shell/PeekOriginalButton.js +3 -3
  156. package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
  157. package/dist/inspector/shell/interactionStatePreview.js +112 -0
  158. package/dist/inspector/shell/originalPreview.d.ts +3 -2
  159. package/dist/inspector/shell/originalPreview.js +10 -37
  160. package/dist/inspector/shell/stateValue.js +2 -3
  161. package/dist/inspector/sketch/SketchEditor.js +5 -4
  162. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
  163. package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
  164. package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
  165. package/dist/inspector/sketch/SketchOverlay.js +139 -263
  166. package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
  167. package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
  168. package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
  169. package/dist/inspector/sketch/SketchThumbnail.js +18 -0
  170. package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
  171. package/dist/inspector/sketch/SketchWorkspace.js +69 -34
  172. package/dist/inspector/sketch/capture.js +19 -3
  173. package/dist/inspector/sketch/copySketch.d.ts +10 -0
  174. package/dist/inspector/sketch/copySketch.js +52 -0
  175. package/dist/inspector/sketch/drawingTools.d.ts +17 -0
  176. package/dist/inspector/sketch/drawingTools.js +47 -0
  177. package/dist/inspector/sketch/frameHeading.d.ts +9 -0
  178. package/dist/inspector/sketch/frameHeading.js +30 -0
  179. package/dist/inspector/sketch/freehand.js +3 -3
  180. package/dist/inspector/sketch/geometry.d.ts +6 -0
  181. package/dist/inspector/sketch/geometry.js +19 -0
  182. package/dist/inspector/sketch/history.d.ts +30 -0
  183. package/dist/inspector/sketch/history.js +58 -0
  184. package/dist/inspector/sketch/interaction.d.ts +1 -3
  185. package/dist/inspector/sketch/interaction.js +6 -9
  186. package/dist/inspector/sketch/model.d.ts +35 -1
  187. package/dist/inspector/sketch/model.js +116 -61
  188. package/dist/inspector/sketch/persistence.d.ts +14 -0
  189. package/dist/inspector/sketch/persistence.js +63 -7
  190. package/dist/inspector/sketch/raster.d.ts +6 -2
  191. package/dist/inspector/sketch/raster.js +43 -17
  192. package/dist/inspector/sketch/store.d.ts +1 -1
  193. package/dist/inspector/sketch/store.js +1 -1
  194. package/dist/inspector/sketch/videoFrames.d.ts +6 -0
  195. package/dist/inspector/sketch/videoFrames.js +81 -0
  196. package/dist/inspector/styleEditors/BackgroundEditor.js +3 -2
  197. package/dist/inspector/styleEditors/BorderEditor.js +30 -130
  198. package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
  199. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
  200. package/dist/inspector/styleEditors/LayoutSection.js +2 -2
  201. package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
  202. package/dist/inspector/styleEditors/borderModel.js +114 -0
  203. package/dist/inspector/styleEditors/layoutValue.js +2 -3
  204. package/dist/inspector/tokens/TokenField.js +40 -7
  205. package/dist/inspector/tokens/TokensPanel.js +2 -1
  206. package/dist/inspector/tokens/editActions.d.ts +3 -3
  207. package/dist/inspector/tokens/editActions.js +15 -3
  208. package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
  209. package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
  210. package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
  211. package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
  212. package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
  213. package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
  214. package/dist/inspector/tokens/resolution.d.ts +6 -3
  215. package/dist/inspector/tokens/resolution.js +91 -112
  216. package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
  217. package/dist/inspector/tokens/tokenEdits.js +1 -1
  218. package/dist/inspector/ui/InlineStyleWarning.js +2 -2
  219. package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
  220. package/dist/inspector/ui/InspectorStyle.js +26 -0
  221. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  222. package/dist/inspector/ui/PopoverListbox.js +3 -3
  223. package/dist/inspector/ui/Toast.d.ts +14 -0
  224. package/dist/inspector/ui/Toast.js +30 -0
  225. package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
  226. package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
  227. package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
  228. package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
  229. package/dist/inspector/ui/customCursors.d.ts +5 -0
  230. package/dist/inspector/ui/customCursors.js +32 -6
  231. package/dist/inspector/ui/styles.js +12 -10
  232. package/dist/inspector/workspace/commands.js +1 -2
  233. package/dist/inspector/workspace/controller.js +1 -2
  234. package/dist/inspector/workspace/handoff.js +3 -2
  235. package/dist/inspector/workspace/sentItems.d.ts +16 -0
  236. package/dist/inspector/workspace/sentItems.js +33 -0
  237. package/dist/inspector/workspace/timeline.d.ts +24 -10
  238. package/dist/inspector/workspace/timeline.js +118 -96
  239. package/dist/internal/agent-protocol.d.ts +50 -13
  240. package/dist/internal/agent-protocol.js +145 -19
  241. package/dist/nudge-ui.mjs +81 -65
  242. package/dist/project/mcpPackage.d.ts +8 -0
  243. package/dist/project/mcpPackage.js +60 -0
  244. package/package.json +13 -3
  245. package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
  246. package/dist/inspector/canvas/measurementProjection.js +0 -16
  247. package/dist/inspector/changes/changesLog.d.ts +0 -47
  248. package/dist/inspector/changes/changesLog.js +0 -247
  249. package/dist/inspector/changes/draftChanges.d.ts +0 -56
  250. package/dist/inspector/changes/draftChanges.js +0 -300
  251. package/dist/inspector/conformance/projection.d.ts +0 -2
  252. package/dist/inspector/conformance/projection.js +0 -3
  253. package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
  254. package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
  255. package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
  256. package/dist/inspector/overlay/InspectorOverlay.js +0 -332
  257. package/dist/inspector/overlay/elementSelector.d.ts +0 -1
  258. package/dist/inspector/overlay/elementSelector.js +0 -111
  259. package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
  260. package/dist/inspector/sketch/SketchChanges.js +0 -72
  261. package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
  262. package/dist/inspector/sketch/SketchEntryButton.js +0 -11
  263. package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
  264. package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
@@ -1,27 +1,34 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { Dialog } from "@base-ui/react/dialog";
4
- import { IconCheck, IconX, } from "@tabler/icons-react";
5
- import { Button } from "../ui/Button.js";
6
- import { IconButton } from "../ui/IconButton.js";
7
4
  import { getSketchViewport } from "./capture.js";
8
- import { SKETCH_ANNOTATION_RADIUS, SKETCH_LIMITS, SKETCH_STROKE_COLOR, SKETCH_STROKE_OUTLINE, SKETCH_STROKE_WIDTH, } from "./model.js";
9
- import { findSketchElementAt, translateSketchAnnotation, translateSketchStroke, } from "./geometry.js";
5
+ import { SKETCH_LIMITS, SKETCH_STROKE_OUTLINE, SKETCH_RECTANGLE_STROKE_WIDTH, SKETCH_STROKE_WIDTH, } from "./model.js";
6
+ import { findErasedSketchStrokeIds } from "./geometry.js";
7
+ import { EMPTY_SKETCH_HISTORY, addSketchStroke, eraseSketchStrokes, redoSketchEdit, undoSketchEdit, } from "./history.js";
8
+ import { resetSketchDrawingTool, useSketchDrawingTools } from "./drawingTools.js";
10
9
  import { SketchPromptPanel } from "./SketchPromptPanel.js";
11
10
  import { SketchSvgLayer } from "./freehand.js";
12
11
  import { useInspectorOpen } from "../shell/openStore.js";
13
- import { DRAG_CURSOR, SKETCH_CURSOR } from "../ui/customCursors.js";
12
+ import { isEditableEvent } from "../shell/shortcuts.js";
13
+ import { ERASER_CURSOR, SKETCH_CURSOR } from "../ui/customCursors.js";
14
14
  import { useTemporaryAppInteraction } from "../canvas/viewStore.js";
15
- import { Tooltip } from "../ui/Tooltip.js";
16
15
  import { portalContainer } from "../ui/portalContainer.js";
16
+ /** Eraser reach around the pointer in screen pixels, matching the cursor ring. */
17
+ const ERASER_RADIUS_PX = 8;
18
+ function sketchCursor(tool) {
19
+ if (tool === "eraser")
20
+ return ERASER_CURSOR;
21
+ if (tool === "rectangle")
22
+ return "crosshair";
23
+ return SKETCH_CURSOR;
24
+ }
17
25
  function clamp(value, maximum) {
18
26
  return Math.max(0, Math.min(maximum, value));
19
27
  }
20
- function pointFromEvent(event, canvas, viewport) {
21
- const bounds = canvas.getBoundingClientRect();
28
+ function pointFromEvent(event, viewport) {
22
29
  return {
23
- x: clamp(((event.clientX - bounds.left) / Math.max(1, bounds.width)) * viewport.width, viewport.width) + viewport.scrollX,
24
- y: clamp(((event.clientY - bounds.top) / Math.max(1, bounds.height)) * viewport.height, viewport.height) + viewport.scrollY,
30
+ x: clamp(((event.clientX - viewport.offsetX) / Math.max(1, viewport.displayWidth)) * viewport.width, viewport.width) + viewport.scrollX,
31
+ y: clamp(((event.clientY - viewport.offsetY) / Math.max(1, viewport.displayHeight)) * viewport.height, viewport.height) + viewport.scrollY,
25
32
  };
26
33
  }
27
34
  function distance(left, right) {
@@ -36,15 +43,6 @@ function visibleStroke(stroke, viewport) {
36
43
  })),
37
44
  };
38
45
  }
39
- function visibleAnnotation(annotation, viewport) {
40
- return {
41
- ...annotation,
42
- point: {
43
- x: annotation.point.x - viewport.scrollX,
44
- y: annotation.point.y - viewport.scrollY,
45
- },
46
- };
47
- }
48
46
  function rectanglePoints(start, end) {
49
47
  const left = Math.min(start.x, end.x);
50
48
  const right = Math.max(start.x, end.x);
@@ -58,58 +56,52 @@ function rectanglePoints(start, end) {
58
56
  { x: left, y: top },
59
57
  ];
60
58
  }
61
- function annotationEditorStyle(annotation, viewport) {
62
- const width = 280;
63
- const height = 52;
64
- return {
65
- left: clamp(annotation.point.x - viewport.scrollX + SKETCH_ANNOTATION_RADIUS + 8, Math.max(8, viewport.width - width - 8)),
66
- top: clamp(annotation.point.y - viewport.scrollY + SKETCH_ANNOTATION_RADIUS + 8, Math.max(8, viewport.height - height - 8)),
67
- };
68
- }
69
- export function SketchOverlay({ open, hostElement, initialTool = "pen", onCancel, onDone }) {
59
+ export function SketchOverlay({ open, hostElement, selectTarget, onCancel, onDone }) {
70
60
  const temporaryAppInteraction = useTemporaryAppInteraction();
71
61
  const inspectorOpen = useInspectorOpen();
72
62
  const [viewport, setViewport] = useState(() => getSketchViewport(hostElement));
73
- const [tool, setTool] = useState("pen");
63
+ const drawing = useSketchDrawingTools();
64
+ const tool = drawing.tool;
74
65
  const [description, setDescription] = useState("");
75
- const [strokes, setStrokes] = useState([]);
76
- const [redoStrokes, setRedoStrokes] = useState([]);
77
- const [annotations, setAnnotations] = useState([]);
78
- const [activeAnnotationId, setActiveAnnotationId] = useState(null);
79
- const [activeAnnotationOriginal, setActiveAnnotationOriginal] = useState(null);
80
- const [annotationDraft, setAnnotationDraft] = useState("");
66
+ const [history, setHistory] = useState(EMPTY_SKETCH_HISTORY);
81
67
  const [draftPoints, setDraftPoints] = useState([]);
82
68
  const [submitting, setSubmitting] = useState(false);
83
69
  const [error, setError] = useState(null);
84
70
  const svgRef = useRef(null);
85
- const annotationInputRef = useRef(null);
71
+ const targetRef = useRef(hostElement);
86
72
  const pointerId = useRef(null);
87
- const moveState = useRef(null);
73
+ const draftPointsRef = useRef([]);
88
74
  const draftPointLimitReached = useRef(false);
89
- const activeAnnotation = annotations.find((annotation) => annotation.id === activeAnnotationId) ?? null;
75
+ const eraserPoint = useRef(null);
76
+ // Identifies the current eraser drag so it undoes as a single edit.
77
+ const eraseGesture = useRef(0);
78
+ const strokes = history.strokes;
79
+ function updateDraftPoints(next) {
80
+ draftPointsRef.current = next;
81
+ setDraftPoints(next);
82
+ }
90
83
  useEffect(() => {
91
84
  if (!open)
92
85
  return;
86
+ targetRef.current = hostElement;
93
87
  setViewport(getSketchViewport(hostElement));
94
- // Sketch starts with a freehand pen. The separate Annotate entry point
95
- // still opens directly in annotation mode without the old live toolbar.
96
- setTool(initialTool);
88
+ resetSketchDrawingTool();
97
89
  setDescription("");
98
- setStrokes([]);
99
- setRedoStrokes([]);
100
- setAnnotations([]);
101
- setActiveAnnotationId(null);
102
- setActiveAnnotationOriginal(null);
103
- setAnnotationDraft("");
104
- setDraftPoints([]);
105
- moveState.current = null;
90
+ setHistory(EMPTY_SKETCH_HISTORY);
91
+ updateDraftPoints([]);
92
+ // Closing mid-stroke unmounts the surface before its pointerup arrives.
93
+ pointerId.current = null;
94
+ eraserPoint.current = null;
106
95
  setSubmitting(false);
107
96
  setError(null);
108
- }, [hostElement, initialTool, open]);
97
+ // Choosing the first stroke's frame must preserve the stroke already in progress.
98
+ }, [open]);
109
99
  useEffect(() => {
110
100
  if (!open)
111
101
  return;
102
+ targetRef.current = hostElement;
112
103
  const updateViewport = () => setViewport(getSketchViewport(hostElement));
104
+ updateViewport();
113
105
  window.addEventListener("resize", updateViewport);
114
106
  window.addEventListener("scroll", updateViewport, true);
115
107
  const frameWindow = typeof HTMLIFrameElement !== "undefined"
@@ -125,155 +117,90 @@ export function SketchOverlay({ open, hostElement, initialTool = "pen", onCancel
125
117
  frameWindow?.removeEventListener("scroll", updateViewport, true);
126
118
  };
127
119
  }, [hostElement, open]);
128
- useEffect(() => {
129
- if (activeAnnotationId !== null)
130
- annotationInputRef.current?.focus();
131
- }, [activeAnnotationId]);
132
120
  useEffect(() => {
133
121
  if (open)
134
122
  svgRef.current?.focus();
135
123
  }, [open]);
136
- function openAnnotation(annotation) {
137
- setActiveAnnotationId(annotation.id);
138
- setActiveAnnotationOriginal(annotation.description);
139
- setAnnotationDraft(annotation.description);
140
- setError(null);
141
- }
142
- function annotationAt(point) {
143
- return [...annotations].reverse().find((annotation) => distance(annotation.point, point) <= SKETCH_ANNOTATION_RADIUS * 1.5) ?? null;
144
- }
145
- function commitActiveAnnotation() {
146
- if (activeAnnotationId === null)
147
- return annotations;
148
- const description = annotationDraft.trim();
149
- if (!description) {
150
- setError("Add a short description for this annotation before continuing.");
151
- annotationInputRef.current?.focus();
152
- return null;
153
- }
154
- const next = annotations.map((annotation) => annotation.id === activeAnnotationId
155
- ? { ...annotation, description }
156
- : annotation);
157
- setAnnotations(next);
158
- setActiveAnnotationId(null);
159
- setActiveAnnotationOriginal(null);
160
- setAnnotationDraft("");
161
- setError(null);
162
- return next;
163
- }
164
- function cancelActiveAnnotation() {
165
- if (activeAnnotationId !== null && activeAnnotationOriginal === "") {
166
- setAnnotations((current) => current.filter((annotation) => annotation.id !== activeAnnotationId));
124
+ // Listen on the window rather than the dialog: the drawing options live in
125
+ // the canvas toolbar, so focus is often outside the dialog while sketching.
126
+ // Text fields keep their native undo.
127
+ useEffect(() => {
128
+ if (!open)
129
+ return;
130
+ function onKeyDown(event) {
131
+ if (!(event.metaKey || event.ctrlKey) || event.altKey || isEditableEvent(event))
132
+ return;
133
+ const key = event.key.toLowerCase();
134
+ const isRedo = (key === "z" && event.shiftKey) || (key === "y" && !event.shiftKey);
135
+ const isUndo = key === "z" && !event.shiftKey;
136
+ if (!isRedo && !isUndo)
137
+ return;
138
+ event.preventDefault();
139
+ event.stopPropagation();
140
+ setHistory((current) => isRedo ? redoSketchEdit(current) : undoSketchEdit(current));
167
141
  }
168
- setActiveAnnotationId(null);
169
- setActiveAnnotationOriginal(null);
170
- setAnnotationDraft("");
171
- setError(null);
142
+ window.addEventListener("keydown", onKeyDown, true);
143
+ return () => window.removeEventListener("keydown", onKeyDown, true);
144
+ }, [open]);
145
+ function eraseAlong(start, end, drawingViewport) {
146
+ const radius = ERASER_RADIUS_PX * (drawingViewport.width / Math.max(1, drawingViewport.displayWidth));
147
+ const gesture = eraseGesture.current;
148
+ setHistory((current) => eraseSketchStrokes(current, findErasedSketchStrokeIds(start, end, current.strokes, radius), gesture));
172
149
  }
173
150
  function onPointerDown(event) {
174
- if (submitting || pointerId.current !== null || moveState.current !== null)
151
+ if (submitting || pointerId.current !== null)
175
152
  return;
176
- const point = pointFromEvent(event, event.currentTarget, viewport);
177
- if (tool === "move") {
178
- event.preventDefault();
179
- const element = findSketchElementAt(point, strokes, annotations);
180
- if (!element)
181
- return;
182
- moveState.current = { pointerId: event.pointerId, element, lastPoint: point };
183
- event.currentTarget.setPointerCapture(event.pointerId);
184
- return;
185
- }
186
- if (tool === "annotate") {
187
- event.preventDefault();
188
- if (activeAnnotationId !== null) {
189
- if (commitActiveAnnotation())
190
- setError("Select Annotate again, then click another area.");
191
- return;
192
- }
193
- const existing = annotationAt(point);
194
- if (existing) {
195
- openAnnotation(existing);
196
- return;
197
- }
198
- if (annotations.length >= SKETCH_LIMITS.annotations) {
199
- setError(`A sketch can contain at most ${SKETCH_LIMITS.annotations} annotations.`);
153
+ let drawingViewport = getSketchViewport(targetRef.current);
154
+ if (selectTarget) {
155
+ const target = selectTarget({ x: event.clientX, y: event.clientY }, history.undo.length > 0 || history.redo.length > 0 ? targetRef.current : null);
156
+ if (!target)
200
157
  return;
158
+ if (target !== targetRef.current) {
159
+ targetRef.current = target;
160
+ drawingViewport = getSketchViewport(target);
161
+ setViewport(drawingViewport);
201
162
  }
202
- const usedNumbers = new Set(annotations.map((annotation) => annotation.number));
203
- let nextNumber = 1;
204
- while (usedNumbers.has(nextNumber))
205
- nextNumber += 1;
206
- const annotation = {
207
- id: `annotation-${Date.now()}-${Math.random().toString(36).slice(2)}`,
208
- number: nextNumber,
209
- point,
210
- description: "",
211
- };
212
- setAnnotations((current) => [...current, annotation]);
213
- setActiveAnnotationId(annotation.id);
214
- setActiveAnnotationOriginal("");
215
- setAnnotationDraft("");
216
- setError(null);
217
- return;
218
163
  }
164
+ if (event.clientX < drawingViewport.offsetX || event.clientX > drawingViewport.offsetX + drawingViewport.displayWidth
165
+ || event.clientY < drawingViewport.offsetY || event.clientY > drawingViewport.offsetY + drawingViewport.displayHeight)
166
+ return;
167
+ const point = pointFromEvent(event, drawingViewport);
168
+ // Drawing cancels the browser's focus change, so move focus here
169
+ // explicitly; otherwise undo keeps going to the note or a toolbar button.
219
170
  event.preventDefault();
171
+ svgRef.current?.focus({ preventScroll: true });
220
172
  pointerId.current = event.pointerId;
221
- draftPointLimitReached.current = false;
222
173
  event.currentTarget.setPointerCapture(event.pointerId);
223
- setDraftPoints([point]);
224
- }
225
- function onPointerMove(event) {
226
- const activeMove = moveState.current;
227
- if (activeMove?.pointerId === event.pointerId) {
228
- event.preventDefault();
229
- const point = pointFromEvent(event, event.currentTarget, viewport);
230
- const delta = {
231
- x: point.x - activeMove.lastPoint.x,
232
- y: point.y - activeMove.lastPoint.y,
233
- };
234
- if (delta.x !== 0 || delta.y !== 0) {
235
- const bounds = { width: SKETCH_LIMITS.imageEdge, height: SKETCH_LIMITS.imageEdge };
236
- if (activeMove.element.kind === "stroke") {
237
- setStrokes((current) => current.map((stroke) => stroke.id === activeMove.element.id
238
- ? translateSketchStroke(stroke, delta, bounds)
239
- : stroke));
240
- }
241
- else {
242
- setAnnotations((current) => current.map((annotation) => annotation.id === activeMove.element.id
243
- ? translateSketchAnnotation(annotation, delta, bounds)
244
- : annotation));
245
- }
246
- activeMove.lastPoint = point;
247
- }
174
+ if (tool === "eraser") {
175
+ eraserPoint.current = point;
176
+ eraseGesture.current += 1;
177
+ eraseAlong(point, point, drawingViewport);
248
178
  return;
249
179
  }
180
+ draftPointLimitReached.current = false;
181
+ updateDraftPoints([point]);
182
+ }
183
+ function onPointerMove(event) {
250
184
  if (pointerId.current !== event.pointerId)
251
185
  return;
252
186
  event.preventDefault();
253
- const point = pointFromEvent(event, event.currentTarget, viewport);
254
- setDraftPoints((previous) => {
255
- const last = previous.at(-1);
256
- if (last && distance(last, point) < 1)
257
- return previous;
258
- if (previous.length >= SKETCH_LIMITS.points) {
259
- draftPointLimitReached.current = true;
260
- return previous;
261
- }
262
- return [...previous, point];
263
- });
264
- }
265
- function finishPointer(event) {
266
- const activeMove = moveState.current;
267
- if (activeMove?.pointerId === event.pointerId) {
268
- moveState.current = null;
269
- try {
270
- event.currentTarget.releasePointerCapture(event.pointerId);
271
- }
272
- catch {
273
- // The pointer may already have been released by the browser.
274
- }
187
+ const point = pointFromEvent(event, viewport);
188
+ if (eraserPoint.current) {
189
+ eraseAlong(eraserPoint.current, point, viewport);
190
+ eraserPoint.current = point;
275
191
  return;
276
192
  }
193
+ const previous = draftPointsRef.current;
194
+ const last = previous.at(-1);
195
+ if (last && distance(last, point) < 1)
196
+ return;
197
+ if (previous.length >= SKETCH_LIMITS.points) {
198
+ draftPointLimitReached.current = true;
199
+ return;
200
+ }
201
+ updateDraftPoints([...previous, point]);
202
+ }
203
+ function finishPointer(event) {
277
204
  if (pointerId.current !== event.pointerId)
278
205
  return;
279
206
  pointerId.current = null;
@@ -283,92 +210,46 @@ export function SketchOverlay({ open, hostElement, initialTool = "pen", onCancel
283
210
  catch {
284
211
  // The pointer may already have been released by the browser.
285
212
  }
213
+ if (eraserPoint.current) {
214
+ eraserPoint.current = null;
215
+ return;
216
+ }
217
+ const previous = draftPointsRef.current;
218
+ updateDraftPoints([]);
286
219
  if (draftPointLimitReached.current) {
287
220
  draftPointLimitReached.current = false;
288
- setDraftPoints([]);
289
221
  setError(`A stroke can contain at most ${SKETCH_LIMITS.points.toLocaleString()} points. The incomplete stroke was discarded.`);
290
222
  return;
291
223
  }
292
- setDraftPoints((previous) => {
293
- if (previous.length === 0)
294
- return previous;
295
- const points = tool === "rectangle" && previous.length > 1
296
- ? rectanglePoints(previous[0], previous.at(-1))
297
- : previous;
298
- const totalPoints = strokes.reduce((total, stroke) => total + stroke.points.length, 0) + points.length;
299
- if (strokes.length >= SKETCH_LIMITS.strokes || totalPoints > SKETCH_LIMITS.points) {
300
- setError("The sketch has reached its stroke limit. Undo a mark before drawing again.");
301
- return [];
302
- }
303
- if (points.length < 2)
304
- return [];
305
- if (tool === "rectangle" && distance(previous[0], previous.at(-1)) < 1)
306
- return [];
307
- const stroke = {
308
- id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2)}`,
309
- kind: tool === "rectangle" ? "rectangle" : "freehand",
310
- width: SKETCH_STROKE_WIDTH,
311
- color: SKETCH_STROKE_COLOR,
312
- outlineColor: SKETCH_STROKE_OUTLINE,
313
- points: points.map((point) => ({ ...point })),
314
- };
315
- setStrokes((current) => [...current, stroke]);
316
- setRedoStrokes([]);
317
- return [];
318
- });
319
- }
320
- function undo() {
321
- setStrokes((current) => {
322
- const last = current.at(-1);
323
- if (!last)
324
- return current;
325
- setRedoStrokes((redo) => [...redo, last]);
326
- return current.slice(0, -1);
327
- });
328
- }
329
- function redo() {
330
- setRedoStrokes((current) => {
331
- const last = current.at(-1);
332
- if (!last)
333
- return current;
334
- setStrokes((existing) => [...existing, last]);
335
- return current.slice(0, -1);
336
- });
337
- }
338
- function onEditorKeyDown(event) {
339
- const mod = event.metaKey || event.ctrlKey;
340
- if (mod && event.key.toLowerCase() === "z") {
341
- event.preventDefault();
342
- event.stopPropagation();
343
- if (event.shiftKey)
344
- redo();
345
- else
346
- undo();
347
- }
348
- else if (mod && event.key.toLowerCase() === "y") {
349
- event.preventDefault();
350
- event.stopPropagation();
351
- redo();
352
- }
353
- }
354
- function onAnnotationKeyDown(event) {
355
- if (event.key === "Enter") {
356
- event.preventDefault();
357
- commitActiveAnnotation();
358
- }
359
- else if (event.key === "Escape") {
360
- event.preventDefault();
361
- cancelActiveAnnotation();
224
+ if (previous.length === 0)
225
+ return;
226
+ const points = tool === "rectangle" && previous.length > 1
227
+ ? rectanglePoints(previous[0], previous.at(-1))
228
+ : previous;
229
+ const totalPoints = strokes.reduce((total, stroke) => total + stroke.points.length, 0) + points.length;
230
+ if (strokes.length >= SKETCH_LIMITS.strokes || totalPoints > SKETCH_LIMITS.points) {
231
+ setError("The sketch has reached its stroke limit. Erase a mark before drawing again.");
232
+ return;
362
233
  }
234
+ if (points.length < 2)
235
+ return;
236
+ if (tool === "rectangle" && distance(previous[0], previous.at(-1)) < 1)
237
+ return;
238
+ const stroke = {
239
+ id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2)}`,
240
+ kind: tool === "rectangle" ? "rectangle" : "freehand",
241
+ width: tool === "rectangle" ? SKETCH_RECTANGLE_STROKE_WIDTH : SKETCH_STROKE_WIDTH,
242
+ color: drawing.color.value,
243
+ outlineColor: SKETCH_STROKE_OUTLINE,
244
+ points: points.map((point) => ({ ...point })),
245
+ };
246
+ setHistory((current) => addSketchStroke(current, stroke));
363
247
  }
364
248
  async function done() {
365
249
  if (submitting)
366
250
  return;
367
- const committedAnnotations = commitActiveAnnotation();
368
- if (committedAnnotations === null)
369
- return;
370
251
  const trimmedDescription = description.trim();
371
- if (strokes.length === 0 && committedAnnotations.length === 0 && trimmedDescription === "") {
252
+ if (strokes.length === 0 && trimmedDescription === "") {
372
253
  setError("Add a mark or a short description before saving the sketch.");
373
254
  return;
374
255
  }
@@ -381,10 +262,6 @@ export function SketchOverlay({ open, hostElement, initialTool = "pen", onCancel
381
262
  ...stroke,
382
263
  points: stroke.points.map((point) => ({ ...point })),
383
264
  })),
384
- annotations: committedAnnotations.map((annotation) => ({
385
- ...annotation,
386
- point: { ...annotation.point },
387
- })),
388
265
  viewport: getSketchViewport(hostElement),
389
266
  });
390
267
  }
@@ -394,12 +271,11 @@ export function SketchOverlay({ open, hostElement, initialTool = "pen", onCancel
394
271
  }
395
272
  }
396
273
  const visibleStrokes = strokes.map((stroke) => visibleStroke(stroke, viewport));
397
- const visibleAnnotations = annotations.map((annotation) => visibleAnnotation(annotation, viewport));
398
274
  const draftStroke = draftPoints.length > 0 ? {
399
275
  id: "draft",
400
276
  kind: tool === "rectangle" ? "rectangle" : "freehand",
401
- width: SKETCH_STROKE_WIDTH,
402
- color: SKETCH_STROKE_COLOR,
277
+ width: tool === "rectangle" ? SKETCH_RECTANGLE_STROKE_WIDTH : SKETCH_STROKE_WIDTH,
278
+ color: drawing.color.value,
403
279
  outlineColor: SKETCH_STROKE_OUTLINE,
404
280
  points: (tool === "rectangle" && draftPoints.length > 1
405
281
  ? rectanglePoints(draftPoints[0], draftPoints.at(-1))
@@ -411,10 +287,10 @@ export function SketchOverlay({ open, hostElement, initialTool = "pen", onCancel
411
287
  if (!open)
412
288
  return null;
413
289
  return (_jsx(Dialog.Root, { open: open, modal: false, disablePointerDismissal: true, onOpenChange: (nextOpen) => { if (!nextOpen && !submitting)
414
- onCancel(); }, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { style: { visibility: temporaryAppInteraction ? "hidden" : undefined }, className: "sketch__backdrop sketch__live-backdrop", "data-inspector-open": inspectorOpen ? "true" : "false", "data-test": "sketch-live-backdrop" }), _jsxs(Dialog.Popup, { style: { visibility: temporaryAppInteraction ? "hidden" : undefined }, className: "sketch__popup sketch__live-popup", "data-inspector-open": inspectorOpen ? "true" : "false", "data-test": "sketch-live-editor", onKeyDown: onEditorKeyDown, children: [_jsx(Dialog.Title, { className: "sketch__title sketch__sr-only", children: "Sketch viewport" }), _jsx(Dialog.Description, { className: "sketch__description sketch__sr-only", children: "Draw directly over the live interface, then add a short note for the sketch." }), _jsxs("div", { className: "sketch__canvas-frame sketch__live-frame", style: {
290
+ onCancel(); }, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { style: { visibility: temporaryAppInteraction ? "hidden" : undefined }, className: "sketch__backdrop sketch__live-backdrop", "data-inspector-open": inspectorOpen ? "true" : "false", "data-test": "sketch-live-backdrop" }), _jsxs(Dialog.Popup, { style: { visibility: temporaryAppInteraction ? "hidden" : undefined }, className: "sketch__popup sketch__live-popup", "data-inspector-open": inspectorOpen ? "true" : "false", "data-test": "sketch-live-editor", children: [_jsx(Dialog.Title, { className: "sketch__title sketch__sr-only", children: "Sketch viewport" }), _jsx(Dialog.Description, { className: "sketch__description sketch__sr-only", children: "Draw directly over the live interface, then add a short note for the sketch." }), selectTarget ? (_jsx("svg", { className: "sketch__target-surface", "aria-hidden": "true", style: { cursor: sketchCursor(tool) }, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: finishPointer, onPointerCancel: finishPointer })) : null, _jsx("div", { className: "sketch__canvas-frame sketch__live-frame", style: {
415
291
  left: viewport.offsetX,
416
292
  top: viewport.offsetY,
417
293
  width: viewport.displayWidth,
418
294
  height: viewport.displayHeight,
419
- }, children: [_jsx("svg", { ref: svgRef, className: `sketch__canvas sketch__live-canvas sketch__canvas--${tool}`, width: viewport.width, height: viewport.height, viewBox: `0 0 ${viewport.width} ${viewport.height}`, preserveAspectRatio: "none", pointerEvents: "all", style: { cursor: tool === "move" ? DRAG_CURSOR : SKETCH_CURSOR }, tabIndex: 0, "aria-label": "Sketch over the live interface", onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: finishPointer, onPointerCancel: finishPointer, children: _jsx(SketchSvgLayer, { strokes: draftStroke ? [...visibleStrokes, draftStroke] : visibleStrokes, annotations: visibleAnnotations }) }), activeAnnotation ? (_jsxs("div", { className: "sketch__annotation-editor", role: "dialog", "aria-label": `Annotation ${activeAnnotation.number}`, style: annotationEditorStyle(activeAnnotation, viewport), children: [_jsxs("label", { className: "sketch__sr-only", htmlFor: "sketch-annotation-description", children: ["Annotation ", activeAnnotation.number, " description"] }), _jsx("input", { ref: annotationInputRef, id: "sketch-annotation-description", value: annotationDraft, maxLength: SKETCH_LIMITS.description, placeholder: "Describe this area\u2026", autoComplete: "off", "data-test": "sketch-live-annotation-description", onChange: (event) => setAnnotationDraft(event.target.value), onKeyDown: onAnnotationKeyDown }), _jsx(Tooltip, { children: _jsx(IconButton, { label: "Cancel annotation", variant: "quiet", size: "compact", "data-test": "sketch-live-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-live-annotation-save", onClick: () => { commitActiveAnnotation(); }, children: [_jsx(IconCheck, { size: "var(--icon-size-small)", "aria-hidden": "true" }), "Add"] })] })) : null] }), _jsx(SketchPromptPanel, { dataTest: "sketch-live-prompt", className: "sketch__live-note-panel", description: description, error: error, saving: submitting, autoFocus: true, onDescriptionChange: setDescription, onDone: done, onCancel: onCancel })] })] }) }));
295
+ }, children: _jsx("svg", { ref: svgRef, className: `sketch__canvas sketch__live-canvas sketch__canvas--${tool}`, width: viewport.width, height: viewport.height, viewBox: `0 0 ${viewport.width} ${viewport.height}`, preserveAspectRatio: "none", pointerEvents: "all", style: { cursor: sketchCursor(tool) }, tabIndex: 0, "aria-label": "Sketch over the live interface", onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: finishPointer, onPointerCancel: finishPointer, children: _jsx(SketchSvgLayer, { strokes: draftStroke ? [...visibleStrokes, draftStroke] : visibleStrokes }) }) }), _jsx(SketchPromptPanel, { dataTest: "sketch-live-prompt", className: "sketch__live-note-panel", description: description, error: error, saving: submitting, autoFocus: true, onDescriptionChange: setDescription, onDone: done, onCancel: onCancel })] })] }) }));
420
296
  }
@@ -1,4 +1,4 @@
1
- import type { ReactElement } from "react";
1
+ import { type ReactElement } from "react";
2
2
  export interface SketchPromptPanelProps {
3
3
  readonly dataTest: string;
4
4
  readonly className?: string;
@@ -1,8 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconAlertTriangleFilled } from "@tabler/icons-react";
3
+ import { useRef } from "react";
2
4
  import { Button } from "../ui/Button.js";
3
5
  import { SKETCH_LIMITS } from "./model.js";
4
6
  /** The small note editor shared by a new live sketch and saved sketch layers. */
5
7
  export function SketchPromptPanel({ dataTest, className, description, error = null, disabled = false, saving = false, autoFocus = false, onDescriptionChange, onDone, onCancel, }) {
8
+ // Keep the last message while the banner collapses so its text does not vanish mid-animation.
9
+ const lastError = useRef(error);
10
+ if (error)
11
+ lastError.current = error;
12
+ const bannerMessage = error ?? lastError.current;
6
13
  function onKeyDown(event) {
7
14
  if (event.key === "Escape" && onCancel) {
8
15
  event.preventDefault();
@@ -14,5 +21,5 @@ export function SketchPromptPanel({ dataTest, className, description, error = nu
14
21
  void onDone();
15
22
  }
16
23
  }
17
- return (_jsxs("section", { className: `sketch__prompt-panel${className ? ` ${className}` : ""}`, role: "dialog", "aria-label": "Sketch note", "data-test": dataTest, children: [_jsx("label", { className: "sketch__prompt-label sketch__sr-only", htmlFor: `${dataTest}-input`, children: "Sketch note" }), _jsx("textarea", { id: `${dataTest}-input`, className: "sketch__prompt-input", value: description, rows: 2, maxLength: SKETCH_LIMITS.description, placeholder: "Add a note to your sketch", "aria-label": "Sketch note", "data-test": `${dataTest}-input`, autoFocus: autoFocus, disabled: disabled, onChange: (event) => onDescriptionChange(event.target.value), onKeyDown: onKeyDown }), _jsxs("div", { className: "sketch__prompt-footer", children: [error ? _jsx("span", { className: "sketch__prompt-error", role: "status", children: error }) : _jsx("span", {}), _jsx(Button, { variant: "primary", size: "compact", type: "button", "data-test": `${dataTest}-done`, disabled: disabled || saving, onClick: () => { void onDone(); }, children: saving ? "Saving…" : "Done" })] })] }));
24
+ return (_jsxs("section", { className: `sketch__prompt-panel${className ? ` ${className}` : ""}`, role: "dialog", "aria-label": "Sketch note", "data-test": dataTest, children: [_jsx("div", { className: "sketch__prompt-error", "data-open": error ? "true" : "false", "aria-hidden": error ? undefined : true, children: _jsx("div", { className: "sketch__prompt-error-clip", children: _jsxs("div", { className: "sketch__prompt-error-body", role: "status", children: [_jsx(IconAlertTriangleFilled, { className: "sketch__prompt-error-icon", size: 16, "aria-hidden": "true" }), _jsx("span", { children: bannerMessage })] }) }) }), _jsx("label", { className: "sketch__prompt-label sketch__sr-only", htmlFor: `${dataTest}-input`, children: "Sketch note" }), _jsx("textarea", { id: `${dataTest}-input`, className: "sketch__prompt-input", value: description, rows: 2, maxLength: SKETCH_LIMITS.description, placeholder: "Add a note to your sketch", "aria-label": "Sketch note", "data-test": `${dataTest}-input`, autoFocus: autoFocus, disabled: disabled, onChange: (event) => onDescriptionChange(event.target.value), onKeyDown: onKeyDown }), _jsx("div", { className: "sketch__prompt-footer", children: _jsx(Button, { variant: "primary", size: "compact", type: "button", "data-test": `${dataTest}-done`, disabled: disabled || saving, onClick: () => { void onDone(); }, children: saving ? "Saving…" : "Done" }) })] }));
18
25
  }
@@ -0,0 +1,5 @@
1
+ import type { ReactElement } from "react";
2
+ import type { SketchQueueItem } from "./model.js";
3
+ export declare function SketchThumbnail({ item }: {
4
+ readonly item: SketchQueueItem;
5
+ }): ReactElement;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ export function SketchThumbnail({ item }) {
4
+ const [url, setUrl] = useState(null);
5
+ const hasMarks = item.document.strokes.length > 0 || (item.document.annotations?.length ?? 0) > 0;
6
+ useEffect(() => {
7
+ if (!hasMarks) {
8
+ setUrl(null);
9
+ return;
10
+ }
11
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function")
12
+ return;
13
+ const next = URL.createObjectURL(item.document.annotatedImage);
14
+ setUrl(next);
15
+ return () => URL.revokeObjectURL(next);
16
+ }, [hasMarks, item.document.annotatedImage]);
17
+ return hasMarks && url ? (_jsx("img", { className: "sketch-thumbnail", src: url, alt: "", "aria-hidden": "true" })) : (_jsx("span", { className: "sketch-thumbnail sketch-thumbnail--empty", "aria-hidden": "true" }));
18
+ }
@@ -1,5 +1,4 @@
1
1
  import type { ReactElement } from "react";
2
- import { beginSketchCapture } from "./interaction.js";
3
2
  import { type SketchLiveDraft } from "./SketchOverlay.js";
4
3
  import type { CapturedSketch } from "./capture.js";
5
4
  import type { SketchStroke } from "./model.js";
@@ -9,5 +8,5 @@ export interface SketchWorkspaceProps {
9
8
  }
10
9
  export declare function projectLiveStrokes(draft: SketchLiveDraft, captured: CapturedSketch): SketchStroke[];
11
10
  export declare function SketchWorkspace({ hostElement, projectId }: SketchWorkspaceProps): ReactElement;
12
- export declare function startSketchCapture(hostElement: HTMLElement | null, initialTool?: Parameters<typeof beginSketchCapture>[0]): void;
11
+ export declare function startSketchCapture(hostElement: HTMLElement | null): void;
13
12
  export declare function editSketch(sketchId: string): void;