Skip to content
Merged
Show file tree
Hide file tree
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
9 changes: 9 additions & 0 deletions .changeset/pink-eyes-make.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@plait/draw': patch
---

support movingPoint align to neighbor point when resize or create arrow line

remove alignPoints handing on hitElement is undefined since it has been handled in before

add some notes arrow line align
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@ import {
} from '../../utils';
import { PRIMARY_COLOR, PlaitCommonElementRef, getDirectionByIndex, getXDistanceBetweenPoint, moveXOfPoint } from '@plait/common';
import { BOARD_TO_PRE_COMMIT } from './with-arrow-line-auto-complete';
import { DrawPointerType, LINE_AUTO_COMPLETE_HOVERED_DIAMETER, LINE_AUTO_COMPLETE_HOVERED_OPACITY } from '../../constants';
import { LINE_AUTO_COMPLETE_HOVERED_DIAMETER, LINE_AUTO_COMPLETE_HOVERED_OPACITY } from '../../constants';
import { ArrowLineAutoCompleteGenerator } from '../../generators';
import { ArrowLineShape, PlaitArrowLine, PlaitDrawElement, PlaitGeometry, PlaitSwimlane, SwimlaneDrawSymbols } from '../../interfaces';
import { getGeometryGeneratorByShape } from '../../utils/shape';

const PREVIEW_ARROW_LINE_DISTANCE = 100;

/*
Purpose: Hover-driven auto-complete preview for arrow lines.
- When hovering a selected shape’s auto-complete point, show a hint circle.
- Offset a temporary shape and build an elbow arrow from the hit edge.
- Rotate points by element angle; compute target connection; bind target id.
- Store temporary arrow/shape for pre-commit; commit on click via auto-complete plugin.
Lifecycle: pointerMove (hit/preview) → pointerLeave/globalPointerUp cleanup.
*/
export const withArrowLineAutoCompleteReaction = (board: PlaitBoard) => {
const { pointerMove, pointerLeave, globalPointerUp } = board;
let reactionG: SVGGElement | null = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export type PreCommitRef = { temporaryArrowLineElement: PlaitArrowLine; temporar

export const BOARD_TO_PRE_COMMIT = new WeakMap<PlaitBoard, PreCommitRef>();

/*
Purpose: Create arrow line via auto-complete drag from a shape edge.
- On pointerDown at an auto-complete point, set elbow mode and capture source.
- On drag (beyond buffer), choose nearest crossing on the source edge if available.
- Rotate source point; build temporary elbow arrow while moving.
- On release, insert the arrow and select; otherwise commit preview pair if present.
Lifecycle: pointerDown → pointerMove (create temp) → pointerUp insert/commit → reset.
*/
export const withArrowLineAutoComplete = (board: PlaitBoard) => {
const { pointerDown, pointerMove, globalPointerUp, touchMove } = board;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import { isResizingByCondition } from '@plait/common';
import { LineResizeHandle } from '../../utils/position/line';
import { drawBoundReaction, getHitShape, getSnappingRef } from '../../utils';

/*
Purpose: Visual feedback for snapping/binding arrow-line endpoints to shapes.
- Active when using arrow-line pointers or resizing source/target handles.
- Detects target shape under cursor and whether edge/connector snapping applies.
- Draws bound outline/mask and a connector marker at the snap point.
- Applies rotation to reaction graphics for angled (rotated) shapes.
Lifecycle: pointerMove → detect hit/snapping → render reaction → pointerUp cleanup.
*/
export const withArrowLineBoundReaction = (board: PlaitBoard) => {
const { pointerMove, pointerUp } = board;

Expand Down
46 changes: 25 additions & 21 deletions packages/draw/src/plugins/arrow-line/with-arrow-line-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,32 @@ export const withArrowLineResize = (board: PlaitBoard) => {
const hitElement = getSnappingShape(board, resizeState.endPoint);
if (resizeRef.handle === LineResizeHandle.source || resizeRef.handle === LineResizeHandle.target) {
const object = resizeRef.handle === LineResizeHandle.source ? source : target;
points[handleIndex] = resizeState.endPoint;
// axis alignment relative to adjacent point, even when snapping to a shape
const neighborPoint = handleIndex === 0 ? points[1] : points[points.length - 2];
// TODO: need handle the neighbor point is not key point(it will moving along moving point)
// such as below case
// [
// [
// 341.2536906953894,
// 83.94690212817761
// ],
// [
// 492.8570148670465,
// 43.86424319286277
// ],
// [
// 492.8570148670465,
// 175.99593084572632
// ],
// [
// 661.8118177564218,
// 175.99593084572632
// ]
// ]
const alignedEndPoint = neighborPoint ? alignPoints(neighborPoint, resizeState.endPoint) : resizeState.endPoint;
points[handleIndex] = alignedEndPoint;
if (hitElement) {
object.connection = getHitConnection(board, resizeState.endPoint, hitElement);
object.connection = getHitConnection(board, alignedEndPoint, hitElement);
object.boundId = hitElement.id;
} else {
object.connection = undefined;
Expand Down Expand Up @@ -119,25 +142,6 @@ export const withArrowLineResize = (board: PlaitBoard) => {
}
}
}

if (!hitElement) {
handleIndex = resizeRef.handle === LineResizeHandle.addHandle ? handleIndex + 1 : handleIndex;
const drawPoints = getArrowLinePoints(board, resizeRef.element);
const newPoints = [...points];
newPoints[0] = drawPoints[0];
newPoints[newPoints.length - 1] = drawPoints[drawPoints.length - 1];
if (
resizeRef.element.shape !== ArrowLineShape.elbow ||
(resizeRef.element.shape === ArrowLineShape.elbow && newPoints.length === 2)
) {
newPoints.forEach((point, index) => {
if (index === handleIndex) return;
if (points[handleIndex]) {
points[handleIndex] = alignPoints(point, points[handleIndex]);
}
});
}
}
DrawTransforms.resizeArrowLine(board, { points, source, target }, resizeRef.path as Path);
},
afterResize: (resizeRef: ResizeRef<PlaitArrowLine, LineResizeHandle>) => {
Expand Down
9 changes: 5 additions & 4 deletions packages/draw/src/utils/arrow-line/arrow-line-basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,9 @@ export const handleArrowLineCreating = (
lineShapeG: SVGGElement,
options?: Pick<PlaitArrowLine, 'strokeColor' | 'strokeWidth'>
) => {
const hitElement = getSnappingShape(board, movingPoint);
const targetConnection = hitElement ? getHitConnection(board, movingPoint, hitElement) : undefined;
const alignedMovingPoint = alignPoints(sourcePoint, movingPoint);
const hitElement = getSnappingShape(board, alignedMovingPoint);
const targetConnection = hitElement ? getHitConnection(board, alignedMovingPoint, hitElement) : undefined;
const sourceConnection = sourceElement ? getHitConnection(board, sourcePoint, sourceElement) : undefined;
const targetBoundId = hitElement ? hitElement.id : undefined;
const lineGenerator = new ArrowLineShapeGenerator(board);
Expand All @@ -227,7 +228,7 @@ export const handleArrowLineCreating = (
targetMarker && delete memorizedLatest.target;
const temporaryLineElement = createArrowLineElement(
lineShape,
[sourcePoint, movingPoint],
[sourcePoint, alignedMovingPoint],
{ marker: sourceMarker || ArrowLineMarkerType.none, connection: sourceConnection, boundId: sourceElement?.id },
{ marker: targetMarker || ArrowLineMarkerType.arrow, connection: targetConnection, boundId: targetBoundId },
[],
Expand All @@ -239,7 +240,7 @@ export const handleArrowLineCreating = (
);
const linePoints = getArrowLinePoints(board, temporaryLineElement);
const otherPoint = linePoints[0];
temporaryLineElement.points[1] = alignPoints(otherPoint, movingPoint);
temporaryLineElement.points[1] = alignPoints(otherPoint, alignedMovingPoint);
lineGenerator.processDrawing(temporaryLineElement, lineShapeG);
PlaitBoard.getElementTopHost(board).append(lineShapeG);
return temporaryLineElement;
Expand Down
Loading