diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx index cb845358..bf27894f 100644 --- a/src/components/Edges/wrapEdge.tsx +++ b/src/components/Edges/wrapEdge.tsx @@ -13,7 +13,7 @@ interface EdgeWrapperProps { labelStyle?: CSSProperties; labelShowBg?: boolean; labelBgStyle: CSSProperties; - onClick: (edge: Edge) => void; + onClick?: (edge: Edge) => void; animated: boolean; selected: boolean; isInteractive: boolean; @@ -46,7 +46,10 @@ export default (EdgeComponent: ComponentType) => { } store.dispatch.setSelectedElements({ id, source, target }); - onClick({ id, source, target, type }); + + if (onClick) { + onClick({ id, source, target, type }); + } }; return ( diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index 0c8b1376..7d310ff9 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -10,16 +10,27 @@ import { Node, XYPosition, Transform, ElementId, NodeComponentProps, WrapNodePro const getMouseEvent = (evt: MouseEvent | TouchEvent) => typeof TouchEvent !== 'undefined' && evt instanceof TouchEvent ? evt.touches[0] : (evt as MouseEvent); -const onStart = ( - evt: MouseEvent | TouchEvent, - onDragStart: (node: Node) => void, - id: ElementId, - type: string, - data: any, - setOffset: (pos: XYPosition) => void, - transform: Transform, - position: XYPosition -): false | void => { +interface OnDragStartParams { + evt: MouseEvent | TouchEvent; + id: ElementId; + type: string; + data: any; + setOffset: (pos: XYPosition) => void; + transform: Transform; + position: XYPosition; + onNodeDragStart?: (node: Node) => void; +} + +const onStart = ({ + evt, + onNodeDragStart, + id, + type, + data, + setOffset, + transform, + position, +}: OnDragStartParams): false | void => { const startEvt = getMouseEvent(evt); const scaledClient: XYPosition = { @@ -33,16 +44,21 @@ const onStart = ( store.dispatch.setSelectedElements({ id, type } as Node); setOffset({ x: offsetX, y: offsetY }); - onDragStart(node); + + if (onNodeDragStart) { + onNodeDragStart(node); + } }; -const onDrag = ( - evt: MouseEvent | TouchEvent, - setDragging: (isDragging: boolean) => void, - id: ElementId, - offset: XYPosition, - transform: Transform -): void => { +interface OnDragParams { + evt: MouseEvent | TouchEvent; + setDragging: (isDragging: boolean) => void; + id: ElementId; + offset: XYPosition; + transform: Transform; +} + +const onDrag = ({ evt, setDragging, id, offset, transform }: OnDragParams): void => { const dragEvt = getMouseEvent(evt); const scaledClient = { @@ -60,16 +76,27 @@ const onDrag = ( }); }; -const onStop = ( - onDragStop: (node: Node) => void, - onClick: (node: Node) => void, - isDragging: boolean, - setDragging: (isDragging: boolean) => void, - id: ElementId, - type: string, - position: XYPosition, - data: any -): void => { +interface OnDragStopParams { + isDragging: boolean; + setDragging: (isDragging: boolean) => void; + id: ElementId; + type: string; + position: XYPosition; + data: any; + onNodeDragStop?: (node: Node) => void; + onClick?: (node: Node) => void; +} + +const onStop = ({ + onNodeDragStop, + onClick, + isDragging, + setDragging, + id, + type, + position, + data, +}: OnDragStopParams): void => { const node = { id, type, @@ -77,12 +104,15 @@ const onStop = ( data, } as Node; - if (!isDragging) { + if (!isDragging && onClick) { return onClick(node); } setDragging(false); - onDragStop(node); + + if (onNodeDragStop) { + onNodeDragStop(node); + } }; export default (NodeComponent: ComponentType) => { @@ -146,9 +176,11 @@ export default (NodeComponent: ComponentType) => { return ( onStart(evt as MouseEvent, onNodeDragStart, id, type, data, setOffset, transform, position)} - onDrag={(evt) => onDrag(evt as MouseEvent, setDragging, id, offset, transform)} - onStop={() => onStop(onNodeDragStop, onClick, isDragging, setDragging, id, type, position, data)} + onStart={(evt) => + onStart({ evt: evt as MouseEvent, onNodeDragStart, id, type, data, setOffset, transform, position }) + } + onDrag={(evt) => onDrag({ evt: evt as MouseEvent, setDragging, id, offset, transform })} + onStop={() => onStop({ onNodeDragStop, onClick, isDragging, setDragging, id, type, position, data })} scale={transform[2]} disabled={!isInteractive} cancel=".nodrag" diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index c9c42de4..d9ae264f 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -16,13 +16,13 @@ import { Elements, NodeTypesType, EdgeTypesType, OnLoadFunc, Node, Edge, Connect export interface GraphViewProps { elements: Elements; - onElementClick: (element: Node | Edge) => void; - onElementsRemove: (elements: Elements) => void; - onNodeDragStart: (node: Node) => void; - onNodeDragStop: (node: Node) => void; - onConnect: (connection: Connection | Edge) => void; - onLoad: OnLoadFunc; - onMove: () => void; + onElementClick?: (element: Node | Edge) => void; + onElementsRemove?: (elements: Elements) => void; + onNodeDragStart?: (node: Node) => void; + onNodeDragStop?: (node: Node) => void; + onConnect?: (connection: Connection | Edge) => void; + onLoad?: OnLoadFunc; + onMove?: () => void; selectionKeyCode: number; nodeTypes: NodeTypesType; edgeTypes: EdgeTypesType; @@ -92,18 +92,21 @@ const GraphView = memo( useEffect(() => { updateDimensions(); - setOnConnect(onConnect); window.onresize = updateDimensions; + if (onConnect) { + setOnConnect(onConnect); + } + return () => { window.onresize = null; }; }, []); - useD3Zoom(zoomPane, onMove, selectionKeyPressed); + useD3Zoom({ zoomPane, onMove, selectionKeyPressed }); useEffect(() => { - if (state.d3Initialised) { + if (state.d3Initialised && onLoad) { onLoad({ fitView, zoomIn, @@ -111,7 +114,7 @@ const GraphView = memo( project, }); } - }, [state.d3Initialised]); + }, [state.d3Initialised, onLoad]); useEffect(() => { setSnapGrid({ snapToGrid, snapGrid }); diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index db410d66..05f1e32c 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -6,9 +6,9 @@ import { Node, Transform, NodeTypesType, WrapNodeProps, Elements, Edge } from '. interface NodeRendererProps { nodeTypes: NodeTypesType; - onElementClick: (element: Node | Edge) => void; - onNodeDragStart: (node: Node) => void; - onNodeDragStop: (node: Node) => void; + onElementClick?: (element: Node | Edge) => void; + onNodeDragStart?: (node: Node) => void; + onNodeDragStop?: (node: Node) => void; onlyRenderVisibleNodes?: boolean; } @@ -22,7 +22,7 @@ function renderNode( const nodeType = node.type || 'default'; const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType; if (!props.nodeTypes[nodeType]) { - console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`); + console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`); } const isSelected = selectedElements ? selectedElements.some(({ id }) => id === node.id) : false; diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 32d4c749..5d46899a 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -26,14 +26,14 @@ import '../../style.css'; export interface ReactFlowProps extends Omit, 'onLoad'> { elements: Elements; - onElementClick: (element: Node | Edge) => void; - onElementsRemove: (elements: Elements) => void; - onNodeDragStart: (node: Node) => void; - onNodeDragStop: (node: Node) => void; - onConnect: (connection: Edge | Connection) => void; - onLoad: OnLoadFunc; - onMove: () => void; - onSelectionChange: (elements: Elements | null) => void; + onElementClick?: (element: Node | Edge) => void; + onElementsRemove?: (elements: Elements) => void; + onNodeDragStart?: (node: Node) => void; + onNodeDragStop?: (node: Node) => void; + onConnect?: (connection: Edge | Connection) => void; + onLoad?: OnLoadFunc; + onMove?: () => void; + onSelectionChange?: (elements: Elements | null) => void; nodeTypes: NodeTypesType; edgeTypes: EdgeTypesType; connectionLineType: string; @@ -106,13 +106,6 @@ const ReactFlow = ({ ReactFlow.displayName = 'ReactFlow'; ReactFlow.defaultProps = { - onElementClick: () => {}, - onElementsRemove: () => {}, - onNodeDragStart: () => {}, - onNodeDragStop: () => {}, - onConnect: () => {}, - onLoad: () => {}, - onMove: () => {}, nodeTypes: { input: InputNode, default: DefaultNode, diff --git a/src/hooks/useD3Zoom.ts b/src/hooks/useD3Zoom.ts index cc9726c8..fc0527e3 100644 --- a/src/hooks/useD3Zoom.ts +++ b/src/hooks/useD3Zoom.ts @@ -4,20 +4,26 @@ import { select, event } from 'd3-selection'; import { useStoreState, useStoreActions } from '../store/hooks'; +interface UseD3ZoomParams { + zoomPane: MutableRefObject; + selectionKeyPressed: boolean; + onMove?: () => void; +} + const d3ZoomInstance = d3Zoom .zoom() .scaleExtent([0.5, 2]) .filter(() => !event.button); -export default (zoomPane: MutableRefObject, onMove: () => void, shiftPressed: boolean): void => { - const state = useStoreState(s => ({ +export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => { + const state = useStoreState((s) => ({ transform: s.transform, d3Selection: s.d3Selection, d3Zoom: s.d3Zoom, })); - const initD3 = useStoreActions(actions => actions.initD3); - const updateTransform = useStoreActions(actions => actions.updateTransform); + const initD3 = useStoreActions((actions) => actions.initD3); + const updateTransform = useStoreActions((actions) => actions.updateTransform); useEffect(() => { if (zoomPane.current) { @@ -27,7 +33,7 @@ export default (zoomPane: MutableRefObject, onMove: () => void, }, []); useEffect(() => { - if (shiftPressed) { + if (selectionKeyPressed) { d3ZoomInstance.on('zoom', null); } else { d3ZoomInstance.on('zoom', () => { @@ -37,7 +43,9 @@ export default (zoomPane: MutableRefObject, onMove: () => void, updateTransform(event.transform); - onMove(); + if (onMove) { + onMove(); + } }); if (state.d3Selection && state.d3Zoom) { @@ -53,5 +61,5 @@ export default (zoomPane: MutableRefObject, onMove: () => void, return () => { d3ZoomInstance.on('zoom', null); }; - }, [shiftPressed]); + }, [selectionKeyPressed]); }; diff --git a/src/hooks/useGlobalKeyHandler.ts b/src/hooks/useGlobalKeyHandler.ts index 48ca0e8d..c30c1f2f 100644 --- a/src/hooks/useGlobalKeyHandler.ts +++ b/src/hooks/useGlobalKeyHandler.ts @@ -7,7 +7,7 @@ import { Elements, Node } from '../types'; interface HookParams { deleteKeyCode: number; - onElementsRemove: (elements: Elements) => void; + onElementsRemove?: (elements: Elements) => void; } export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => { @@ -19,7 +19,7 @@ export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => { const deleteKeyPressed = useKeyPress(deleteKeyCode); useEffect(() => { - if (deleteKeyPressed && state.selectedElements) { + if (onElementsRemove && deleteKeyPressed && state.selectedElements) { let elementsToRemove = state.selectedElements; // we also want to remove the edges if only one node is selected diff --git a/src/types/index.ts b/src/types/index.ts index 20868fcf..a368ab77 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -108,7 +108,8 @@ export interface NodeComponentProps { yPos?: number; targetPosition?: Position; sourcePosition?: Position; - onClick?: (node: Node) => void | undefined; + onClick?: (node: Node) => void; + onNodeDragStart?: (node: Node) => void; onNodeDragStop?: (node: Node) => void; style?: CSSProperties; } @@ -122,9 +123,9 @@ export interface WrapNodeProps { xPos: number; yPos: number; isInteractive: boolean; - onClick: (node: Node) => void | undefined; - onNodeDragStart: (node: Node) => void; - onNodeDragStop: (node: Node) => void; + onClick?: (node: Node) => void; + onNodeDragStart?: (node: Node) => void; + onNodeDragStop?: (node: Node) => void; style?: CSSProperties; sourcePosition?: Position; targetPosition?: Position;