import { Node, NodeEditorData, NodeId, Port, Position, GridSettings } from '../../../types/core'; import { nodeEditorActions, NodeEditorAction } from './actions'; import { BoundActionCreators } from '../../../utils/typedActions'; import { Settings } from '../../../hooks/useSettings'; import { SettingsManager } from '../../../settings/SettingsManager'; import { NodeDefinition } from '../../../types/NodeDefinition'; /** * @file Node editor context type definitions and hook for accessing editor state and utilities */ import * as React from "react"; export type NodeEditorUtils = { /** * Snap a position to grid based on grid settings */ snapToGrid: (position: Position, gridSettings: GridSettings) => Position; /** * Find which group (if any) a node should belong to */ findContainingGroup: (node: Node, allNodes: Record, nodeDefinitions: NodeDefinition[]) => NodeId | null; /** * Get all child nodes of a group */ getGroupChildren: (groupId: NodeId, allNodes: Record) => Node[]; /** * Check if a node is inside a group's bounds */ isNodeInsideGroup: (node: Node, groupNode: Node, nodeDefinitions: NodeDefinition[]) => boolean; }; export type NodeEditorContextValue = { state: NodeEditorData; /** * Returns the latest state reference synchronously, even before React commits a render. * Useful for pointer handlers that must reflect immediate results (e.g. disconnect → reconnect). */ getState: () => NodeEditorData; dispatch: React.Dispatch; actions: BoundActionCreators; actionCreators: typeof nodeEditorActions; /** * Set of port keys ("nodeId:portId") that are part of any connection. * Do not mutate. */ connectedPorts: Set; /** * Per-node connected port ids (portId only; nodeId already known). * Values are stable references when contents are unchanged. * Do not mutate. */ connectedPortIdsByNode: ReadonlyMap>; isLoading: boolean; isSaving: boolean; handleSave: () => Promise; /** * Returns ordered ports for a node, suitable for UI rendering. * Preserves definition order and applies node-specific overrides. */ getNodePorts: (nodeId: NodeId) => Port[]; /** * Returns the latest node reference from the current state (not memoized to render). * Useful for event handlers that need fresh data before React commits a render. */ getNodeById: (nodeId: NodeId) => Node | undefined; settings: Settings; settingsManager?: SettingsManager; updateSetting: (key: string, value: unknown) => void; /** * Utility functions for common node editor operations * These should be used instead of directly importing from utils */ utils: NodeEditorUtils; }; export declare const NodeEditorContext: React.Context; export type NodeEditorStateChange = { action: NodeEditorAction; /** Node ids whose data/geometry/type may have changed */ changedNodeIds: readonly NodeId[]; /** Node ids removed from the graph */ removedNodeIds: readonly NodeId[]; /** True when consumers should recompute from scratch */ fullResync: boolean; /** True when viewport visibility / geometry-dependent consumers should update */ affectsGeometry: boolean; /** True when port structure/positions for changed nodes may have changed */ affectsPorts: boolean; /** True when node ordering for rendering may have changed */ affectsNodeOrder: boolean; /** True when connection-derived caches should update */ affectsConnections: boolean; }; export type NodeEditorApiValue = { dispatch: React.Dispatch; actions: BoundActionCreators; getState: () => NodeEditorData; subscribe: (listener: () => void) => () => void; subscribeToChanges: (listener: (change: NodeEditorStateChange) => void) => () => void; getSortedNodeIds: () => NodeId[]; subscribeToSortedNodeIds: (listener: () => void) => () => void; getConnectedPorts: () => Set; getConnectedPortIdsByNode: () => ReadonlyMap>; subscribeToConnectionDerived: (listener: () => void) => () => void; getNodePorts: (nodeId: NodeId) => Port[]; getNodeById: (nodeId: NodeId) => Node | undefined; }; export declare const NodeEditorApiContext: React.Context; export declare const useNodeEditor: () => NodeEditorContextValue; export declare const useNodeEditorApi: () => NodeEditorApiValue; /** * Selects a derived value from the node editor state without forcing consumers * to re-render for unrelated state changes. */ export declare function useNodeEditorSelector(selector: (state: NodeEditorData) => T, options?: { areEqual?: (a: T, b: T) => boolean; }): T; /** * Subscribe to the stable, rendering-order node id list. * This list only updates when node ordering changes (e.g. add/remove/type change). */ export declare function useNodeEditorSortedNodeIds(): NodeId[]; /** * Subscribe to the derived connected port-key Set ("nodeId:portId"). * Updates only when connections change. */ export declare function useNodeEditorConnectedPorts(): Set; /** * Subscribe to per-node connected port id Sets (portId-only, keyed by nodeId). * Updates only when connections change. */ export declare function useNodeEditorConnectedPortIdsByNode(): ReadonlyMap>; export declare const useNodeEditorActions: () => BoundActionCreators<{ readonly addNode: ((node: Omit) => { type: "ADD_NODE"; payload: { node: Omit; }; }) & { readonly type: "ADD_NODE"; }; readonly addNodeWithId: ((node: Node) => { type: "ADD_NODE_WITH_ID"; payload: { node: Node; }; }) & { readonly type: "ADD_NODE_WITH_ID"; }; readonly updateNode: ((nodeId: string, updates: Partial) => { type: "UPDATE_NODE"; payload: { nodeId: string; updates: Partial; }; }) & { readonly type: "UPDATE_NODE"; }; readonly deleteNode: ((nodeId: string) => { type: "DELETE_NODE"; payload: { nodeId: string; }; }) & { readonly type: "DELETE_NODE"; }; readonly moveNode: ((nodeId: string, position: Position) => { type: "MOVE_NODE"; payload: { nodeId: string; position: Position; }; }) & { readonly type: "MOVE_NODE"; }; readonly moveNodes: ((updates: Record) => { type: "MOVE_NODES"; payload: { updates: Record; }; }) & { readonly type: "MOVE_NODES"; }; readonly addConnection: ((connection: Omit, options?: { allowMultiToPort?: boolean; } | undefined) => { type: "ADD_CONNECTION"; payload: { connection: Omit; allowMultiToPort: boolean | undefined; }; }) & { readonly type: "ADD_CONNECTION"; }; readonly deleteConnection: ((connectionId: string) => { type: "DELETE_CONNECTION"; payload: { connectionId: string; }; }) & { readonly type: "DELETE_CONNECTION"; }; readonly setNodeData: ((data: NodeEditorData) => { type: "SET_NODE_DATA"; payload: { data: NodeEditorData; }; }) & { readonly type: "SET_NODE_DATA"; }; readonly restoreState: ((data: NodeEditorData) => { type: "RESTORE_STATE"; payload: { data: NodeEditorData; }; }) & { readonly type: "RESTORE_STATE"; }; readonly duplicateNodes: ((nodeIds: string[]) => { type: "DUPLICATE_NODES"; payload: { nodeIds: string[]; }; }) & { readonly type: "DUPLICATE_NODES"; }; readonly groupNodes: ((nodeIds: string[], groupId?: string | undefined) => { type: "GROUP_NODES"; payload: { nodeIds: string[]; groupId: string | undefined; }; }) & { readonly type: "GROUP_NODES"; }; readonly ungroupNode: ((groupId: string) => { type: "UNGROUP_NODE"; payload: { groupId: string; }; }) & { readonly type: "UNGROUP_NODE"; }; readonly updateGroupMembership: ((updates: Record) => { type: "UPDATE_GROUP_MEMBERSHIP"; payload: { updates: Record; }; }) & { readonly type: "UPDATE_GROUP_MEMBERSHIP"; }; readonly moveGroupWithChildren: ((groupId: string, delta: { x: number; y: number; }, affectedNodeIds: string[]) => { type: "MOVE_GROUP_WITH_CHILDREN"; payload: { groupId: string; delta: { x: number; y: number; }; affectedNodeIds: string[]; }; }) & { readonly type: "MOVE_GROUP_WITH_CHILDREN"; }; readonly autoLayout: ((layoutType: "force" | "hierarchical" | "grid", selectedOnly?: boolean | undefined) => { type: "AUTO_LAYOUT"; payload: { layoutType: "force" | "hierarchical" | "grid"; selectedOnly: boolean | undefined; }; }) & { readonly type: "AUTO_LAYOUT"; }; readonly copyNodes: ((nodeIds: string[]) => { type: "COPY_NODES"; payload: { nodeIds: string[]; }; }) & { readonly type: "COPY_NODES"; }; readonly pasteNodes: ((offsetX?: number | undefined, offsetY?: number | undefined) => { type: "PASTE_NODES"; payload: { offsetX: number | undefined; offsetY: number | undefined; }; }) & { readonly type: "PASTE_NODES"; }; readonly pruneInvalidConnections: (() => { type: "PRUNE_INVALID_CONNECTIONS"; payload: {}; }) & { readonly type: "PRUNE_INVALID_CONNECTIONS"; }; }>; export declare const useNodeEditorState: () => { state: NodeEditorData; actions: BoundActionCreators<{ readonly addNode: ((node: Omit) => { type: "ADD_NODE"; payload: { node: Omit; }; }) & { readonly type: "ADD_NODE"; }; readonly addNodeWithId: ((node: Node) => { type: "ADD_NODE_WITH_ID"; payload: { node: Node; }; }) & { readonly type: "ADD_NODE_WITH_ID"; }; readonly updateNode: ((nodeId: string, updates: Partial) => { type: "UPDATE_NODE"; payload: { nodeId: string; updates: Partial; }; }) & { readonly type: "UPDATE_NODE"; }; readonly deleteNode: ((nodeId: string) => { type: "DELETE_NODE"; payload: { nodeId: string; }; }) & { readonly type: "DELETE_NODE"; }; readonly moveNode: ((nodeId: string, position: Position) => { type: "MOVE_NODE"; payload: { nodeId: string; position: Position; }; }) & { readonly type: "MOVE_NODE"; }; readonly moveNodes: ((updates: Record) => { type: "MOVE_NODES"; payload: { updates: Record; }; }) & { readonly type: "MOVE_NODES"; }; readonly addConnection: ((connection: Omit, options?: { allowMultiToPort?: boolean; } | undefined) => { type: "ADD_CONNECTION"; payload: { connection: Omit; allowMultiToPort: boolean | undefined; }; }) & { readonly type: "ADD_CONNECTION"; }; readonly deleteConnection: ((connectionId: string) => { type: "DELETE_CONNECTION"; payload: { connectionId: string; }; }) & { readonly type: "DELETE_CONNECTION"; }; readonly setNodeData: ((data: NodeEditorData) => { type: "SET_NODE_DATA"; payload: { data: NodeEditorData; }; }) & { readonly type: "SET_NODE_DATA"; }; readonly restoreState: ((data: NodeEditorData) => { type: "RESTORE_STATE"; payload: { data: NodeEditorData; }; }) & { readonly type: "RESTORE_STATE"; }; readonly duplicateNodes: ((nodeIds: string[]) => { type: "DUPLICATE_NODES"; payload: { nodeIds: string[]; }; }) & { readonly type: "DUPLICATE_NODES"; }; readonly groupNodes: ((nodeIds: string[], groupId?: string | undefined) => { type: "GROUP_NODES"; payload: { nodeIds: string[]; groupId: string | undefined; }; }) & { readonly type: "GROUP_NODES"; }; readonly ungroupNode: ((groupId: string) => { type: "UNGROUP_NODE"; payload: { groupId: string; }; }) & { readonly type: "UNGROUP_NODE"; }; readonly updateGroupMembership: ((updates: Record) => { type: "UPDATE_GROUP_MEMBERSHIP"; payload: { updates: Record; }; }) & { readonly type: "UPDATE_GROUP_MEMBERSHIP"; }; readonly moveGroupWithChildren: ((groupId: string, delta: { x: number; y: number; }, affectedNodeIds: string[]) => { type: "MOVE_GROUP_WITH_CHILDREN"; payload: { groupId: string; delta: { x: number; y: number; }; affectedNodeIds: string[]; }; }) & { readonly type: "MOVE_GROUP_WITH_CHILDREN"; }; readonly autoLayout: ((layoutType: "force" | "hierarchical" | "grid", selectedOnly?: boolean | undefined) => { type: "AUTO_LAYOUT"; payload: { layoutType: "force" | "hierarchical" | "grid"; selectedOnly: boolean | undefined; }; }) & { readonly type: "AUTO_LAYOUT"; }; readonly copyNodes: ((nodeIds: string[]) => { type: "COPY_NODES"; payload: { nodeIds: string[]; }; }) & { readonly type: "COPY_NODES"; }; readonly pasteNodes: ((offsetX?: number | undefined, offsetY?: number | undefined) => { type: "PASTE_NODES"; payload: { offsetX: number | undefined; offsetY: number | undefined; }; }) & { readonly type: "PASTE_NODES"; }; readonly pruneInvalidConnections: (() => { type: "PRUNE_INVALID_CONNECTIONS"; payload: {}; }) & { readonly type: "PRUNE_INVALID_CONNECTIONS"; }; }>; }; export type { NodeEditorData };