@gravity-ui/graph
Version:
Modern graph editor component
214 lines (213 loc) • 7.3 kB
TypeScript
import type { DragState } from "../../../services/drag/types";
import { TBlock } from "../blocks/Block";
import { BlockGroups, BlockGroupsProps } from "./BlockGroups";
import { Group } from "./Group";
/**
* Callback called when block transfer starts (Shift pressed during drag)
* @param blockIds - IDs of blocks being transferred
* @param sourceGroupIds - Set of source group IDs (groups blocks came from)
*/
export type OnTransferStart = (blockIds: TBlock["id"][], sourceGroupIds: Set<string>) => void;
/**
* Callback called when block transfer ends (mouse released or Shift released)
* @param blockIds - IDs of blocks that were transferred
* @param targetGroupId - Target group ID (null if removed from group)
*/
export type OnTransferEnd = (blockIds: TBlock["id"][], targetGroupId: string | null) => void;
/**
* Object representing a change in block's group membership
*/
export type TBlockGroupsTransferGroupChange = {
blockId: TBlock["id"];
sourceGroup?: string | null;
targetGroup?: string | null;
};
/**
* Callback called when blocks' groups change
* @param changes - Array of changes to apply
*/
export type OnBlockGroupChange = (changes: TBlockGroupsTransferGroupChange[]) => void;
export type BlockGroupsTransferLayerProps = BlockGroupsProps & {
/**
* Enable/disable block transfer between groups with Shift+drag.
* Default: true
*/
transferEnabled?: boolean;
/**
* Called when block transfer starts (Shift pressed during drag)
*/
onTransferStart?: OnTransferStart;
/**
* Called when block transfer ends (mouse released or Shift released)
*/
onTransferEnd?: OnTransferEnd;
/**
* Called when a block's group changes
*/
onBlockGroupChange?: OnBlockGroupChange;
/**
* If true, blocks will move when the group is dragged
*/
updateBlocksOnDrag?: boolean;
};
type TransferState = {
isTransferring: boolean;
/** All blocks being transferred */
blocks: TBlock[];
/** Source group IDs for each block (null if block was not in a group) */
sourceGroupIds: Set<string>;
/** Current target group ID */
targetGroupId: string | null;
/** Currently highlighted group ID */
highlightedGroupId: string | null;
};
/**
* BlockGroups layer with block-to-group transfer functionality.
*
* ## Features
* - Hold Shift during drag to activate transfer mode
* - Release Shift to deactivate transfer mode and return to normal drag
* - Groups highlight when blocks are dragged over them
* - Multi-block transfer: all selected blocks are transferred together
* - Source groups lock their size during transfer
* - Callbacks for state synchronization with external stores (Redux, MobX, etc.)
*
* ## Basic Usage
* ```typescript
* const layer = graph.addLayer(BlockGroupsTransferLayer, {
* transferEnabled: true,
* draggable: true,
* });
* ```
*
* ## With Automatic Grouping
* ```typescript
* const GroupsLayer = BlockGroupsTransferLayer.withBlockGrouping({
* groupingFn: (blocks) => groupBy(blocks, (b) => b.$state.value.group),
* mapToGroups: (groupId, { rect }) => ({ id: groupId, rect }),
* });
*
* graph.addLayer(GroupsLayer, {
* transferEnabled: true,
* updateBlocksOnDrag: true, // Blocks move with the group
* });
* ```
*
* ## With Redux Integration
* ```typescript
* graph.addLayer(GroupsLayer, {
* onTransferStart: (blockIds, sourceGroupIds) => {
* console.log('Transfer started:', blockIds);
* },
* onBlockGroupChange: (changes) => {
* // Sync with Redux
* changes.forEach(({ blockId, targetGroup }) => {
* store.dispatch(updateBlockGroup({ blockId, groupId: targetGroup }));
* });
* },
* onTransferEnd: (blockIds, targetGroupId) => {
* console.log('Transfer completed:', blockIds, 'to group:', targetGroupId);
* },
* });
* ```
*
* Uses DragService.$state.currentEvent.shiftKey to track Shift state in real-time.
*/
export declare class BlockGroupsTransferLayer<P extends BlockGroupsTransferLayerProps = BlockGroupsTransferLayerProps> extends BlockGroups<P> {
/** Current transfer state */
protected transferState: TransferState;
/** Cleanup function for the drag state subscription */
protected disposeSubscription: (() => void) | null;
protected get isTransferEnabled(): boolean;
protected afterInit(): void;
/**
* Subscribe to DragService state changes
*/
protected subscribeToDragState(): void;
/**
* Handle drag state changes - react to Shift key in real-time
*/
protected handleDragStateChange(dragState: DragState, isShiftPressed: boolean): void;
/**
* Activate transfer mode for currently dragged blocks
*/
protected activateTransfer(dragState: DragState): void;
/**
* Deactivate transfer mode - apply transfer and unlock groups
* Called when Shift is released during drag
*/
protected deactivateTransfer(): void;
/**
* Lock all groups' sizes
*/
protected lockAllGroups(): void;
/**
* Unlock all groups' sizes
*/
protected unlockAllGroups(): void;
protected createIdleState(): TransferState;
/**
* Update highlighting based on cursor position
*/
protected updateHighlight(point: [number, number]): void;
/**
* End transfer on drag end (mouseup) - apply transfer if in transfer mode
*/
protected endTransfer(): void;
/**
* Cancel the transfer operation without applying changes.
*
* This method can be called to abort an ongoing transfer without moving blocks to a new group.
* It will unhighlight groups, unlock sizes, and reset the transfer state.
*
* @example
* ```typescript
* // Cancel transfer on Escape key
* document.addEventListener('keydown', (e) => {
* if (e.key === 'Escape' && layer.isTransferring()) {
* layer.cancelTransfer();
* }
* });
* ```
*/
cancelTransfer(): void;
/**
* Find a group at the given point
*/
protected findGroupAtPoint(point: [number, number]): Group | null;
/**
* Set highlight state for a group directly on the component
*/
protected setGroupHighlight(groupId: string, highlighted: boolean): void;
/**
* Apply the group change to the block
*/
protected applyGroupChange(changes: TBlockGroupsTransferGroupChange[]): void;
/**
* Check if a block transfer is currently in progress.
*
* @returns `true` if transfer mode is active (Shift is pressed during drag), `false` otherwise
*
* @example
* ```typescript
* if (layer.isTransferring()) {
* console.log('Transferring', layer.getTransferringBlocksCount(), 'blocks');
* }
* ```
*/
isTransferring(): boolean;
/**
* Get the number of blocks being transferred in the current operation.
*
* @returns Number of blocks currently being transferred, or 0 if no transfer is in progress
*
* @example
* ```typescript
* const count = layer.getTransferringBlocksCount();
* console.log(`Transferring ${count} block${count !== 1 ? 's' : ''}`);
* ```
*/
getTransferringBlocksCount(): number;
protected unmountLayer(): void;
}
export {};