diff --git a/src/components/Canvas/LineObject.tsx b/src/components/Canvas/LineObject.tsx index e39bb987..50f3b9f2 100644 --- a/src/components/Canvas/LineObject.tsx +++ b/src/components/Canvas/LineObject.tsx @@ -14,6 +14,56 @@ import { selectionHandlers, type KonvaObjectProps } from "./konvaObjectProps"; const HANDLE_VISIBLE_SIZE = 7; const HANDLE_HIT_SIZE = 14; +/** + * Selection outline for a line — two parallel selection-coloured strokes + * offset perpendicular to the body. Drawing alongside (not on top) keeps + * the body's difference-blend intact for reverse (^LRY) lines and matches + * the Illustrator-style stroke selection affordance. Returns null for + * zero-length lines (degenerate input). + * + * Offset breakdown for a 1 px visual gap between body and selection edges: + * bodyStrokeWidth / 2 — half of the body (centre → body edge) + * 0.5 — half of the 1 px selection stroke + * (centre → selection's body-side edge) + * 1 — actual gap requested between the two adjacent + * edges + */ +function LineSelectionOutline({ + x1, y1, x2, y2, + bodyStrokeWidth, + color, +}: { + x1: number; y1: number; x2: number; y2: number; + bodyStrokeWidth: number; + color: string; +}) { + const dx = x2 - x1; + const dy = y2 - y1; + const len = Math.hypot(dx, dy); + if (len === 0) return null; + const off = bodyStrokeWidth / 2 + 1.5; + const px = (-dy / len) * off; + const py = (dx / len) * off; + return ( + <> + + + + ); +} + type LineLabelObject = Extract; type Props = Omit & { obj: LineLabelObject }; @@ -217,12 +267,13 @@ export function LineObject({ globalCompositeOperation={isReverse ? "difference" : "source-over"} /> {isSelected && ( - )} {/* Wide transparent hit area — handles click-to-select and whole-line drag.