@gravity-ui/graph
Version:
Modern graph editor component
186 lines (185 loc) • 6.5 kB
TypeScript
import { GraphMouseEvent } from "../../../../graphEvents";
import { Layer, LayerContext, LayerProps } from "../../../../services/Layer";
import { TCameraState } from "../../../../services/camera/CameraService";
import { TBlockId } from "../../../../store/block/Block";
import { PortState } from "../../../../store/connection/port/Port";
import type { Emitter } from "../../../../utils/Emitter";
import { Point, TPoint } from "../../../../utils/types/shapes";
export type TPortSnapConditionContext = {
sourcePort: PortState;
targetPort: PortState;
cursorPosition: TPoint;
distance: number;
};
export type TPortSnapCondition = (context: TPortSnapConditionContext) => boolean;
/**
* Port metadata structure for PortConnectionLayer
* This structure is stored under PortConnectionLayer.PortMetaKey in port.meta
*/
export interface IPortConnectionMeta {
/** Enable snapping for this port. If false or undefined, port will not participate in snapping */
snappable?: boolean;
/** Custom condition for snapping - validates if connection is allowed */
snapCondition?: TPortSnapCondition;
}
type TIcon = {
path: string;
fill?: string;
stroke?: string;
width: number;
height: number;
viewWidth: number;
viewHeight: number;
};
type LineStyle = {
color: string;
width?: number;
dash: number[];
};
type DrawLineFunction = (start: TPoint, end: TPoint) => {
path: Path2D;
style: LineStyle;
};
type PortConnectionLayerProps = LayerProps & {
createIcon?: TIcon;
point?: TIcon;
drawLine?: DrawLineFunction;
searchRadius?: number;
lineWidth?: number;
lineDash?: number[];
};
declare module "../../../../graphEvents" {
interface GraphEventsDefinitions {
/**
* Port-based event fired when a user initiates a connection from a port.
* Extends connection-create-start with direct port reference.
*/
"port-connection-create-start": (event: CustomEvent<{
blockId: TBlockId;
anchorId: string | undefined;
sourcePort: PortState;
}>) => void;
/**
* Port-based event fired when the connection hovers over a potential target port.
* Extends connection-create-hover with direct port references.
*/
"port-connection-create-hover": (event: CustomEvent<{
sourceBlockId: TBlockId;
sourceAnchorId: string | undefined;
targetBlockId: TBlockId | undefined;
targetAnchorId: string | undefined;
sourcePort: PortState;
targetPort?: PortState;
}>) => void;
/**
* Port-based event fired when a connection is successfully created between two ports.
* Extends connection-created with direct port references.
*/
"port-connection-created": (event: CustomEvent<{
sourceBlockId: TBlockId;
sourceAnchorId?: string;
targetBlockId: TBlockId;
targetAnchorId?: string;
sourcePort: PortState;
targetPort: PortState;
}>) => void;
"port-connection-cancel": (event: CustomEvent<{
sourcePort: PortState;
targetPort: PortState;
}>) => void;
/**
* Port-based event fired when the user releases the mouse button.
* Extends connection-create-drop with direct port references.
*/
"port-connection-create-drop": (event: CustomEvent<{
sourceBlockId: TBlockId;
sourceAnchorId: string;
targetBlockId?: TBlockId;
targetAnchorId?: string;
point: Point;
sourcePort: PortState;
targetPort?: PortState;
}>) => void;
}
}
/**
* PortConnectionLayer - new layer for creating connections, working only with ports
*
* Key differences from ConnectionLayer:
* - Works only with ports, (use Block and Anchor components only to pass more info about ports in Event)
* - Uses findPortAtPoint to detect ports under cursor
* - Metadata is stored under unique PortConnectionLayer.PortMetaKey key
* - More efficient search through spatial index
* - Events extended with sourcePort and targetPort parameters
*
* @example
* ```typescript
* // Configure port for snapping
* port.updatePort({
* meta: {
* [PortConnectionLayer.PortMetaKey]: {
* snappable: true,
* snapCondition: (ctx) => {
* // Custom validation logic
* return true;
* }
* }
* }
* });
* ```
*/
export declare class PortConnectionLayer extends Layer<PortConnectionLayerProps, LayerContext & {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
}> {
/**
* Unique key for port metadata
* Using a symbol prevents conflicts with other layers
*/
static readonly PortMetaKey: unique symbol;
private startState;
private endState;
private sourcePort?;
private targetPort?;
private snappingPortsTree;
private isSnappingTreeOutdated;
private portsUnsubscribe?;
protected enabled: boolean;
constructor(props: PortConnectionLayerProps);
protected afterInit(): void;
protected onCameraChange(_camera: TCameraState): void;
enable: () => void;
disable: () => void;
protected currentListener: Emitter | null;
private isSnappablePort;
protected handleMouseDown: (nativeEvent: GraphMouseEvent) => void;
protected renderEndpoint(ctx: CanvasRenderingContext2D): void;
protected render(): void;
private onStartConnection;
private onMoveNewConnection;
protected selectPort(port: PortState, select: boolean): void;
protected cancelNewConnection(): void;
private onEndNewConnection;
private findNearestSnappingPort;
/**
* Rebuild the RBush spatial index for snapping ports
* Optimization: Only includes ports from components visible in viewport + padding
*/
private rebuildSnappingTree;
/**
* Determine the port type (IN or OUT)
* @param port Port to check
* @returns EAnchorType.IN, EAnchorType.OUT, or null if the port is a block point (no specific direction)
*/
private getPortType;
/**
* Get full event parameters from a port
* Includes both legacy parameters (blockId, anchorId) and new port reference
*/
protected getEventParams(port?: PortState): {
blockId?: TBlockId;
anchorId?: string;
};
unmount(): void;
}
export {};