@gravity-ui/graph
Version:
Modern graph editor component
164 lines (163 loc) • 5.96 kB
TypeScript
import { TComponentState } from "../../../lib/Component";
import { DragContext, DragDiff } from "../../../services/drag";
import { BlockState } from "../../../store/block/Block";
import { GroupState, TGroup, TGroupId } from "../../../store/group/Group";
import { TMeasureTextOptions } from "../../../utils/functions/text";
import { TRect } from "../../../utils/types/shapes";
import { GraphComponent } from "../GraphComponent";
import { TGraphLayerContext } from "../layers/graphLayer/GraphLayer";
import { BlockGroups } from "./BlockGroups";
export type TGroupStyle = {
background: string;
border: string;
borderWidth: number;
selectedBackground: string;
selectedBorder: string;
/** Background color when group is highlighted (block is being dragged over it) */
highlightedBackground: string;
/** Border color when group is highlighted */
highlightedBorder: string;
};
export type TGroupGeometry = {
padding: [number, number, number, number];
};
export type TGroupProps = {
id: TGroupId;
onDragUpdate: (groupId: string, diff: {
deltaX: number;
deltaY: number;
}) => void;
style?: Partial<TGroupStyle>;
geometry?: Partial<TGroupGeometry>;
draggable?: boolean;
};
type TGroupState<T extends TGroup = TGroup> = TComponentState & T & {
rect: TRect;
};
export declare class Group<T extends TGroup = TGroup> extends GraphComponent<TGroupProps, TGroupState<T>, TGraphLayerContext> {
static define(config: {
style?: Partial<TGroupStyle>;
geometry?: Partial<TGroupGeometry>;
}): typeof Group;
get zIndex(): number;
context: TGraphLayerContext;
protected blocks: BlockState[];
protected groupState: GroupState<T>;
cursor: string;
protected style: TGroupStyle;
protected geometry: TGroupGeometry;
/** Whether the group is highlighted (block is being dragged over it) */
protected highlighted: boolean;
/** Whether the group is currently being dragged */
protected isDragging: boolean;
protected dragStartRect: {
x: number;
y: number;
} | null;
protected lastSnappedPos: {
x: number;
y: number;
} | null;
constructor(props: TGroupProps, parent: BlockGroups);
protected handleClick: (event: MouseEvent) => void;
/**
* Set the highlighted state of the group
*/
setHighlighted(highlighted: boolean): void;
/**
* Check if the group is currently highlighted (block is being dragged over it).
*
* This method is useful for custom group components that override the `render()` method
* and need to apply different styling when the group is highlighted during transfer mode.
*
* @returns `true` if the group is highlighted, `false` otherwise
*
* @example
* ```typescript
* class CustomGroup extends Group {
* protected override render() {
* const ctx = this.context.ctx;
* const rect = this.getRect();
*
* // Apply different styles based on state
* if (this.isHighlighted()) {
* ctx.strokeStyle = 'rgba(100, 200, 100, 1)';
* ctx.lineWidth = 3;
* } else if (this.state.selected) {
* ctx.strokeStyle = 'rgba(100, 100, 100, 1)';
* ctx.lineWidth = 2;
* } else {
* ctx.strokeStyle = 'rgba(100, 100, 100, 0.4)';
* ctx.lineWidth = 1;
* }
*
* ctx.beginPath();
* ctx.roundRect(rect.x, rect.y, rect.width, rect.height, 8);
* ctx.stroke();
* }
* }
* ```
*/
isHighlighted(): boolean;
getEntityId(): string;
/**
* Check if group can be dragged based on props.draggable and canDrag setting
*/
isDraggable(): boolean;
/**
* Override to apply snapping or other position transforms during drag.
* Called with the raw (pre-snap) target position computed from the drag start + cumulative diff.
* The default implementation returns the position unchanged (no snapping).
*
* @example
* ```typescript
* protected override snapPosition(x: number, y: number) {
* const grid = 16;
* return { x: Math.round(x / grid) * grid, y: Math.round(y / grid) * grid };
* }
* ```
*/
protected snapPosition(x: number, y: number): {
x: number;
y: number;
};
/**
* Handle drag start - stores the initial rect position and sets isDragging flag.
* Subclasses that override this method should call super.handleDragStart() to preserve this behavior.
*/
handleDragStart(_context: DragContext): void;
/**
* Handle drag update - moves the group rect and notifies via onDragUpdate.
* Uses the cumulative diff from drag start so that snapPosition always operates
* on the absolute target position (avoids error accumulation from per-frame deltas).
* onDragUpdate is called only when the position actually changes.
*/
handleDrag(diff: DragDiff, _context: DragContext): void;
/**
* Handle drag end - clears isDragging flag and drag tracking state.
* Subclasses that override this method should call super.handleDragEnd() to preserve this behavior.
*/
handleDragEnd(_context: DragContext): void;
protected getRect(rect?: TRect): {
x: number;
y: number;
width: number;
height: number;
};
protected subscribeToGroup(): void;
protected unmount(): void;
protected updateHitBox(rect: TRect): void;
protected layoutText(text: string, textParams?: TMeasureTextOptions): {
measures: import("../../../utils/functions/text").TWrapText;
lines: any[];
lineHeight: number;
};
protected renderBody(ctx: CanvasRenderingContext2D, rect?: {
x: number;
y: number;
width: number;
height: number;
}): void;
protected render(): void;
}
export {};