@gravity-ui/graph
Version:
Modern graph editor component
214 lines (213 loc) • 8.71 kB
TypeScript
import { DragContext, DragDiff } from "../../../services/drag";
import { BlockState } from "../../../store/block/Block";
import { TGroup, TGroupId } from "../../../store/group/Group";
import { TRect } from "../../../utils/types/shapes";
import { Group, TGroupGeometry, TGroupStyle } from "./Group";
declare module "../../../graphEvents" {
interface GraphEventsDefinitions {
"group-collapse-change": (event: CustomEvent<{
groupId: TGroupId;
collapsed: boolean;
currentRect: TRect;
nextRect: TRect;
}>) => void;
}
}
/** A single collapse direction axis value. */
export type TCollapseDirection = "start" | "center" | "end";
export interface TCollapsibleGroup extends TGroup {
/** Whether this group is currently collapsed */
collapsed?: boolean;
/**
* The visual rect used when collapsed. When set, the group renders and
* responds to hit-tests using this rect instead of the normal `rect`.
*
* `rect` itself continues to be managed by `withBlockGrouping` (or
* manually) and always reflects the real block bounding box.
*/
collapsedRect?: TRect;
/**
* Where the collapsed header appears relative to the full group rect.
*
* - `x`: `"start"` → left edge | `"center"` → centered | `"end"` → right edge
* - `y`: `"start"` → top edge | `"center"` → centered | `"end"` → bottom edge
*
* Defaults to `{ x: "start", y: "start" }` (top-left corner).
*
* Only used by the default collapse rect computation. Ignored when
* {@link getCollapseRect} is provided.
*/
collapseDirection?: {
x?: TCollapseDirection;
y?: TCollapseDirection;
};
/**
* User-defined function to compute the collapsed rect from the expanded rect.
*
* When not provided, a default implementation computes a
* {@link DEFAULT_COLLAPSED_WIDTH}×{@link DEFAULT_COLLAPSED_HEIGHT} rect
* pinned at the position determined by {@link collapseDirection}.
*
* @example
* ```typescript
* const group: TCollapsibleGroup = {
* id: "my-group",
* rect: { x: 0, y: 0, width: 400, height: 300 },
* component: CollapsibleGroup,
* getCollapseRect: (_group, rect) => ({
* x: rect.x + rect.width / 2 - 100,
* y: rect.y,
* width: 200,
* height: 48,
* }),
* };
* ```
*/
getCollapseRect?: (group: TCollapsibleGroup, expandedRect: TRect) => TRect;
}
/**
* Default collapse rect computation. Produces a rect of the given size
* pinned at the position determined by `direction`.
*
* Exported so users can call it from their custom `getCollapseRect` and
* extend or modify the default behavior.
*
* @param expandedRect - The full group rect before collapsing.
* @param direction - Where the header is pinned (defaults to top-left).
* @param collapsedWidth - Header width (defaults to 200).
* @param collapsedHeight - Header height (defaults to 48).
*/
export declare function computeDefaultCollapseRect(expandedRect: TRect, direction?: {
x?: TCollapseDirection;
y?: TCollapseDirection;
}, collapsedWidth?: number, collapsedHeight?: number): TRect;
/**
* Cached result of {@link CollapsibleGroup.getPortDelegationCounts}; invalidated
* when delegation membership changes (delegate/undelegate), not when rects move.
*/
type TPortDelegationCounts = {
left: number;
right: number;
};
export declare class CollapsibleGroup<T extends TCollapsibleGroup = TCollapsibleGroup> extends Group<T> {
/** Snapshot of `collapsedRect` at drag start; see {@link handleDrag}. */
private dragStartCollapsedRect;
/**
* Lazily computed delegation counts for {@link renderCollapsedView}; cleared when
* delegation changes (delegate/undelegate ports).
*/
private portDelegationCountsCache;
static define(config: {
style?: Partial<TGroupStyle>;
geometry?: Partial<TGroupGeometry>;
}): typeof CollapsibleGroup;
/**
* Extend base subscription to also react to collapsed state on init.
* subscribeSignal fires immediately with the current value, so a group
* that starts with collapsed: true will hide its blocks on mount.
*
* Also handles external collapse state changes: if setGroups() is called
* with collapsed: false while the group is currently collapsed, it expands.
*/
protected subscribeToGroup(): ReturnType<Group["subscribeToGroup"]>;
/**
* Returns the visual rect. When collapsed, returns `collapsedRect`
* (with padding) so the group renders as a compact header.
*/
protected getRect(rect?: TRect): TRect;
/**
* Sets the hitbox to the collapsed rect when collapsed, or the expanded
* rect otherwise. Passes the raw inner rect to super so that base Group's
* updateHitBox can apply padding exactly once.
*/
protected updateHitBox(rect: TRect): void;
/**
* Remember inner rect and collapsed rect so {@link handleDrag} can translate
* `collapsedRect` by the same snapped delta as `rect` (grid snapping in Group).
*/
handleDragStart(context: DragContext): void;
handleDragEnd(context: DragContext): void;
/**
* When dragging a collapsed group, move `collapsedRect` in lockstep with the snapped
* `rect` movement computed by {@link Group.handleDrag} (not per-frame mouse deltas).
*/
handleDrag(diff: DragDiff, context: DragContext): void;
/** Whether this group is currently in the collapsed state. */
isCollapsed(): boolean;
protected getGroupBlocks(): BlockState[];
/**
* Returns the number of ports currently delegated to the left and right
* group edge ports. Only meaningful when the group is collapsed.
*
* Cost is O(blocks × anchors) per call when the cache is cold — typical group
* sizes keep this cheap. The result is cached until delegation changes
* ({@link delegatePorts} / {@link undelegatePorts}); override {@link renderCollapsedView}
* and call a custom counter if you need different invalidation rules.
*/
protected getPortDelegationCounts(): TPortDelegationCounts;
private invalidatePortDelegationCountsCache;
/**
* Compute the collapsed rect for a given full rect.
*
* Uses the user-provided `getCollapseRect` if available, otherwise falls
* back to the direction-based default.
*/
private computeCollapsedRect;
/**
* Collapse the group: set collapsedRect, hide member blocks,
* and redirect their ports to the group edges.
*
* Emits a cancelable `group-collapse-change` event before applying changes.
* If a listener calls `event.preventDefault()`, the collapse is cancelled.
*/
collapse(): void;
/**
* Expand the group: remove collapsedRect, show member blocks, and let
* them resume managing their own ports.
*
* Emits a cancelable `group-collapse-change` event before applying changes.
* If a listener calls `event.preventDefault()`, the expand is cancelled.
*/
expand(): void;
protected unmount(): void;
private applyBlockVisibility;
/**
* Get (or create) the group's left-edge port used as a delegation target.
* Input ports and IN anchors delegate to this port when collapsed.
*/
private getLeftEdgePort;
/**
* Get (or create) the group's right-edge port used as a delegation target.
* Output ports and OUT anchors delegate to this port when collapsed.
*/
private getRightEdgePort;
/**
* Update the group's edge port positions to match the given rect.
*/
private updateGroupPortPositions;
/**
* Delegate all ports of group blocks to the group's edge ports.
*
* - Input port → left-edge port
* - Output port → right-edge port
* - IN anchors → left-edge port
* - OUT anchors → right-edge port
*
* While delegated, block ports mirror the group edge positions.
* When the group is dragged, only the group edge ports need to be
* updated — all delegated ports follow automatically.
*/
private delegatePorts;
/**
* Remove delegation from all ports of group blocks, restoring their
* original positions (saved automatically by the delegation mechanism).
*/
private undelegatePorts;
protected render(): void;
/**
* Render the compact header shown when the group is collapsed.
* Override this method to customise the collapsed appearance.
*/
protected renderCollapsedView(ctx: CanvasRenderingContext2D): void;
}
export {};