Add freehand annotations layer#31
Merged
Merged
Conversation
added 16 commits
May 13, 2026 16:57
Adds an optional annotations layer (toggled via Settings → Enable annotations) that renders on top of the UML diagram and moves/zooms with it. Annotations are view-specific (conceptual/logical/physical) and saved in the .mdlz file. Tools: pen (color, thickness), marker (color, thickness, opacity), text label (color, font size), eraser (size, whole/partial mode). Each drawing tool has a color preset row with 8 swatches and a custom color picker. The toolbox uses Bootstrap Icons. Undo/redo is unified with the existing Edit menu history: every annotation mutation pushes a combined snapshot of nodes, edges, model name and annotations, so Ctrl+Z undoes both diagram and annotation changes in a single stack.
The cursor now reflects the active annotation tool and its configured size: a scaled SVG circle for pen, marker (with semi-transparent fill), and eraser; text cursor for the text tool; default arrow for pointer.
Replace window.prompt with an inline textarea that appears directly on the canvas. Supports multiline text via Shift+Enter, auto-resizes as you type, and commits on Enter or blur. Double-clicking an existing text item opens it for inline editing in place. Rendered text now splits on newlines using tspan elements.
- Align textarea baseline with click position by shifting the foreignObject up by ~85% of the font size; change rendered SVG text to dominantBaseline="alphabetic" to match - Single click on an existing text label in text mode now opens it for inline editing instead of placing a new label on top - While a text input is open, clicking elsewhere only commits the current input; it no longer immediately opens a new one at the click position
Two root causes: - e.preventDefault() was called unconditionally in onPointerDown, which blocks browser focus changes and prevents the textarea blur from firing when clicking elsewhere. Now only called for drawing tools (pen, marker, eraser). - Clicking on existing SVG text items relied on stopPropagation from a child element through React's synthetic event system, which is unreliable for SVG. Replaced with data-annotation-id attribute on each text element and e.target.closest() detection in the SVG's own onPointerDown wrapper.
… for text-item clicks The handleSvgPointerDown wrapper broke new text placement. Replace it with onPointerDownCapture on the <g> element: capture phase fires on <g> before the bubble phase reaches <svg>, so text-item clicks are detected and consumed there. Clicks on empty canvas target <svg> directly and never pass through <g>, so they reach onPointerDown unchanged. Also restore autoFocus on the textarea so focus works reliably inside SVG foreignObject without relying on programmatic el.focus().
The SVG element is not a guaranteed hit target for transparent empty areas, so pointer events could silently miss it. Moving all handlers to the parent div (which always covers inset:0) ensures every click in the overlay is received. The SVG is kept with pointer-events:all so its children remain hittable and the <g> capture handler still fires for text-item clicks before they bubble to the div.
- Restore e.preventDefault() for all tools; rely on explicit blur instead of browser-managed focus changes for text commit - Move all pointer handlers to the overlay div (guaranteed hit surface); SVG has no explicit pointer-events so it inherits from the div — this fixes pan/zoom in pointer mode where the div is pointer-events:none - Blur the active textarea explicitly in handlePointerDown before e.preventDefault() can suppress it; return without placing new text - Detect text-item clicks via onPointerDownCapture on <g>; capture phase fires before the div bubble handler, stopPropagation prevents accidental new-text placement - TextEditor exposes its textarea via textareaRef so the div handler can blur it; editing TextEditor rendered at AnnotationLayer level (not inside AnnotationText) to share the same activeTextareaRef
…f capture handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.mdlzfile, and integrated with the undo/redo historyTest plan
.mdlzfile — annotations restored correctlynpm testpasses