Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions src/components/Canvas/LineObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<KLine
points={[x1 + px, y1 + py, x2 + px, y2 + py]}
stroke={color}
strokeWidth={1}
lineCap="butt"
listening={false}
/>
<KLine
points={[x1 - px, y1 - py, x2 - px, y2 - py]}
stroke={color}
strokeWidth={1}
lineCap="butt"
listening={false}
/>
</>
);
}

type LineLabelObject = Extract<LabelObject, { type: "line" }>;
type Props = Omit<KonvaObjectProps, "obj"> & { obj: LineLabelObject };

Expand Down Expand Up @@ -217,12 +267,13 @@ export function LineObject({
globalCompositeOperation={isReverse ? "difference" : "source-over"}
/>
{isSelected && (
<KLine
points={[dispX1, dispY1, dispX2, dispY2]}
stroke={colors.selection}
strokeWidth={lineStrokeWidth}
lineCap="butt"
listening={false}
<LineSelectionOutline
x1={dispX1}
y1={dispY1}
x2={dispX2}
y2={dispY2}
bodyStrokeWidth={lineStrokeWidth}
color={colors.selection}
/>
)}
{/* Wide transparent hit area — handles click-to-select and whole-line drag.
Expand Down