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
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class ConnectionLayer extends Layer<
*/
protected afterInit(): void {
// Register event listeners with the graphOn wrapper method for automatic cleanup when unmounted
this.onGraphEvent("mousedown", this.handleMouseDown);
this.onGraphEvent("mousedown", this.handleMouseDown, { capture: true });

// Call parent afterInit to ensure proper initialization
super.afterInit();
Expand Down Expand Up @@ -196,12 +196,16 @@ export class ConnectionLayer extends Layer<
if (!initEvent || !target || !this.root?.ownerDocument) {
return;
}
if (initEvent.button !== 0) {
return;
}

if (
this.checkIsShouldStartCreationConnection(target as GraphComponent, initEvent) &&
(isBlock(target) || target instanceof Anchor)
) {
if (isGraphEvent(nativeEvent)) {
nativeEvent.preventGraphEventDefault();
nativeEvent.stopGraphEventPropagation();
}
this.context.graph.dragService.startDrag(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EAnchorType } from "../../../../store/anchor/Anchor";
import { TBlockId } from "../../../../store/block/Block";
import { PortState } from "../../../../store/connection/port/Port";
import type { Emitter } from "../../../../utils/Emitter";
import { vectorDistance } from "../../../../utils/functions";
import { getXY, vectorDistance } from "../../../../utils/functions";
import { stopDragListening } from "../../../../utils/functions/dragListener";
import { render } from "../../../../utils/renderers/render";
import { renderSVG } from "../../../../utils/renderers/svgPath";
Expand Down Expand Up @@ -260,6 +260,11 @@ export class PortConnectionLayer extends Layer<

protected currentListener: Emitter | null = null;

private isSnappablePort(port: PortState): boolean {
const meta = port.meta?.[PortConnectionLayer.PortMetaKey] as IPortConnectionMeta | undefined;
return Boolean(meta?.snappable);
}

protected handleMouseDown = (nativeEvent: GraphMouseEvent): void => {
if (!this.enabled) {
return;
Expand All @@ -269,27 +274,36 @@ export class PortConnectionLayer extends Layer<
if (!initEvent || !this.root?.ownerDocument || !initialComponent) {
return;
}
if (initEvent.button !== 0) {
return;
}
if (!(initialComponent instanceof GraphComponent) || initialComponent.getPorts().length === 0) {
return;
}

const canvas = this.context.graph.getGraphCanvas();
const [screenX, screenY] = getXY(canvas, initEvent);
const [worldX, worldY] = this.context.graph.cameraService.applyToPoint(screenX, screenY);
const searchRadius = this.props.searchRadius || PORT_SEARCH_RADIUS;
const port = this.context.graph.rootStore.connectionsList.ports.findPortAtPointByComponent(
initialComponent,
new Point(worldX, worldY),
searchRadius,
(candidate) => this.isSnappablePort(candidate)
);
if (!port) {
return;
}

if (isGraphEvent(nativeEvent)) {
nativeEvent.preventGraphEventDefault();
nativeEvent.stopGraphEventPropagation();
}
// DragService will provide world coordinates in callbacks
this.currentListener = this.context.graph.dragService.startDrag(
{
onStart: (_event, coords) => {
const point = new Point(coords[0], coords[1]);
const searchRadius = this.props.searchRadius || PORT_SEARCH_RADIUS;

const port = this.context.graph.rootStore.connectionsList.ports.findPortAtPointByComponent(
initialComponent,
point,
searchRadius
);
if (port) {
this.onStartConnection(port, point);
}
this.onStartConnection(port, new Point(coords[0], coords[1]));
},
onUpdate: (event, coords) => this.onMoveNewConnection(event, new Point(coords[0], coords[1])),
onEnd: (_event, coords) => this.onEndNewConnection(new Point(coords[0], coords[1])),
Expand Down Expand Up @@ -410,7 +424,7 @@ export class PortConnectionLayer extends Layer<
// Try to find port at cursor without snapping
const searchRadius = this.props.searchRadius || PORT_SEARCH_RADIUS;
newTargetPort = this.context.graph.rootStore.connectionsList.ports.findPortAtPoint(point, searchRadius, (p) => {
return Boolean(p.owner) && p.id !== this.sourcePort?.id;
return this.isSnappablePort(p) && Boolean(p.owner) && p.id !== this.sourcePort?.id;
});
}

Expand Down Expand Up @@ -498,7 +512,7 @@ export class PortConnectionLayer extends Layer<
// Fallback: try to find port at drop point
const searchRadius = this.props.searchRadius || PORT_SEARCH_RADIUS;
targetPort = this.context.graph.rootStore.connectionsList.ports.findPortAtPoint(point, searchRadius, (p) => {
return Boolean(p.owner) && p.id !== this.sourcePort?.id;
return this.isSnappablePort(p) && Boolean(p.owner) && p.id !== this.sourcePort?.id;
});
}

Expand Down Expand Up @@ -681,9 +695,7 @@ export class PortConnectionLayer extends Layer<
// Skip ports in lookup state (no valid coordinates)
if (port.lookup) continue;

const meta = port.meta?.[PortConnectionLayer.PortMetaKey] as IPortConnectionMeta | undefined;

if (meta?.snappable) {
if (this.isSnappablePort(port)) {
snappingBoxes.push({
minX: port.x - searchRadius,
minY: port.y - searchRadius,
Expand Down
17 changes: 13 additions & 4 deletions src/services/camera/Camera.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventedComponent } from "../../components/canvas/EventedComponent/EventedComponent";
import { GraphComponent } from "../../components/canvas/GraphComponent";
import { TGraphLayerContext } from "../../components/canvas/layers/graphLayer/GraphLayer";
import { GraphMouseEvent } from "../../graphEvents";
import { GraphMouseEvent, isGraphEvent } from "../../graphEvents";
import { Component, ESchedulerPriority } from "../../lib";
import { TComponentProps, TComponentState } from "../../lib/Component";
import { ComponentDescriptor } from "../../lib/CoreComponent";
Expand Down Expand Up @@ -181,6 +182,9 @@ export class Camera extends EventedComponent<TCameraProps, TComponentState, TGra
if (!this.context.graph.rootStore.settings.getConfigFlag("canDragCamera")) {
return;
}
if (isGraphEvent(event) && event.isDefaultPrevented()) {
return;
}
if (isMetaKeyEvent(nativeEvent)) {
return;
}
Expand All @@ -189,9 +193,7 @@ export class Camera extends EventedComponent<TCameraProps, TComponentState, TGra
// Middle button: preventDefault stops DragService (see graph.emit guard)
// and suppresses the browser's native middle-click autoscroll.
event.preventDefault();
} else if (event.detail.target !== this) {
// Left button: let DragService handle drags on blocks/anchors; only pan
// when the click lands on empty canvas (target resolved to Camera).
} else if (event.detail.target !== this && this.isTargetDraggable(event.detail.target)) {
return;
}

Expand All @@ -205,6 +207,13 @@ export class Camera extends EventedComponent<TCameraProps, TComponentState, TGra
.on(EVENTS.DRAG_END, () => this.onDragEnd());
};

/**
* DragService handles draggable graph components; camera pan is deferred in that case.
*/
private isTargetDraggable(target: EventedComponent | undefined): boolean {
return target instanceof GraphComponent && target.isDraggable();
}

private onDragStart(event: MouseEvent) {
this.lastDragEvent = event;
}
Expand Down
Loading