+
-
+
{nodesSelectionActive &&
}
diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx
index df5e68e5..079d6d23 100644
--- a/src/container/NodeRenderer/index.tsx
+++ b/src/container/NodeRenderer/index.tsx
@@ -1,4 +1,4 @@
-import React, { memo, ComponentType } from 'react';
+import React, { memo, ComponentType, MouseEvent } from 'react';
import { useStoreState } from '../../store/hooks';
import { getNodesInside } from '../../utils/graph';
@@ -8,6 +8,10 @@ interface NodeRendererProps {
nodeTypes: NodeTypesType;
selectNodesOnDrag: boolean;
onElementClick?: (element: Node | Edge) => void;
+ onNodeMouseEnter?: (evt: MouseEvent, node: Node) => void;
+ onNodeMouseMove?: (evt: MouseEvent, node: Node) => void;
+ onNodeMouseLeave?: (evt: MouseEvent, node: Node) => void;
+ onNodeContextMenu?: (evt: MouseEvent, node: Node) => void;
onNodeDragStart?: (node: Node) => void;
onNodeDragStop?: (node: Node) => void;
onlyRenderVisibleNodes?: boolean;
@@ -18,7 +22,9 @@ function renderNode(
props: NodeRendererProps,
transform: Transform,
selectedElements: Elements | null,
- isInteractive: boolean
+ nodesDraggable: boolean,
+ nodesConnectable: boolean,
+ elementsSelectable: boolean
) {
const nodeType = node.type || 'default';
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType
;
@@ -37,13 +43,19 @@ function renderNode(
xPos={node.__rf.position.x}
yPos={node.__rf.position.y}
onClick={props.onElementClick}
+ onMouseEnter={props.onNodeMouseEnter}
+ onMouseMove={props.onNodeMouseMove}
+ onMouseLeave={props.onNodeMouseLeave}
+ onContextMenu={props.onNodeContextMenu}
onNodeDragStart={props.onNodeDragStart}
onNodeDragStop={props.onNodeDragStop}
transform={transform}
selected={isSelected}
style={node.style}
className={node.className}
- isInteractive={isInteractive}
+ isDraggable={nodesDraggable}
+ isSelectable={elementsSelectable}
+ isConnectable={nodesConnectable}
sourcePosition={node.sourcePosition}
targetPosition={node.targetPosition}
selectNodesOnDrag={props.selectNodesOnDrag}
@@ -57,7 +69,10 @@ const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRend
const selectedElements = useStoreState((s) => s.selectedElements);
const width = useStoreState((s) => s.width);
const height = useStoreState((s) => s.height);
- const isInteractive = useStoreState((s) => s.isInteractive);
+ const nodesDraggable = useStoreState((s) => s.nodesDraggable);
+ const nodesConnectable = useStoreState((s) => s.nodesConnectable);
+ const elementsSelectable = useStoreState((s) => s.elementsSelectable);
+
const [tX, tY, tScale] = transform;
const transformStyle = {
transform: `translate(${tX}px,${tY}px) scale(${tScale})`,
@@ -69,7 +84,9 @@ const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRend
return (
- {renderNodes.map((node) => renderNode(node, props, transform, selectedElements, isInteractive))}
+ {renderNodes.map((node) =>
+ renderNode(node, props, transform, selectedElements, nodesDraggable, nodesConnectable, elementsSelectable)
+ )}
);
});
diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx
index 82baff23..343e2964 100644
--- a/src/container/ReactFlow/index.tsx
+++ b/src/container/ReactFlow/index.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo, CSSProperties, HTMLAttributes } from 'react';
+import React, { useMemo, CSSProperties, HTMLAttributes, MouseEvent } from 'react';
import cx from 'classnames';
const nodeEnv: string = process.env.NODE_ENV as string;
@@ -17,6 +17,7 @@ import SelectionListener from '../../components/SelectionListener';
import BezierEdge from '../../components/Edges/BezierEdge';
import StraightEdge from '../../components/Edges/StraightEdge';
import StepEdge from '../../components/Edges/StepEdge';
+import SmoothStepEdge from '../../components/Edges/SmoothStepEdge';
import { createEdgeTypes } from '../EdgeRenderer/utils';
import Wrapper from './Wrapper';
import {
@@ -36,6 +37,10 @@ export interface ReactFlowProps extends Omit, 'on
elements: Elements;
onElementClick?: (element: Node | Edge) => void;
onElementsRemove?: (elements: Elements) => void;
+ onNodeMouseEnter?: (evt: MouseEvent, node: Node) => void;
+ onNodeMouseMove?: (evt: MouseEvent, node: Node) => void;
+ onNodeMouseLeave?: (evt: MouseEvent, node: Node) => void;
+ onNodeContextMenu?: (evt: MouseEvent, node: Node) => void;
onNodeDragStart?: (node: Node) => void;
onNodeDragStop?: (node: Node) => void;
onConnect?: (connection: Edge | Connection) => void;
@@ -51,7 +56,9 @@ export interface ReactFlowProps extends Omit, 'on
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
- isInteractive: boolean;
+ nodesDraggable: boolean;
+ nodesConnectable: boolean;
+ elementsSelectable: boolean;
selectNodesOnDrag: boolean;
minZoom: number;
maxZoom: number;
@@ -70,6 +77,10 @@ const ReactFlow = ({
onMove,
onElementsRemove,
onConnect,
+ onNodeMouseEnter,
+ onNodeMouseMove,
+ onNodeMouseLeave,
+ onNodeContextMenu,
onNodeDragStart,
onNodeDragStop,
onSelectionChange,
@@ -80,7 +91,9 @@ const ReactFlow = ({
snapToGrid,
snapGrid,
onlyRenderVisibleNodes,
- isInteractive,
+ nodesDraggable,
+ nodesConnectable,
+ elementsSelectable,
selectNodesOnDrag,
minZoom,
maxZoom,
@@ -96,6 +109,10 @@ const ReactFlow = ({
onLoad={onLoad}
onMove={onMove}
onElementClick={onElementClick}
+ onNodeMouseEnter={onNodeMouseEnter}
+ onNodeMouseMove={onNodeMouseMove}
+ onNodeMouseLeave={onNodeMouseLeave}
+ onNodeContextMenu={onNodeContextMenu}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
nodeTypes={nodeTypesParsed}
@@ -110,7 +127,9 @@ const ReactFlow = ({
snapToGrid={snapToGrid}
snapGrid={snapGrid}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
- isInteractive={isInteractive}
+ nodesDraggable={nodesDraggable}
+ nodesConnectable={nodesConnectable}
+ elementsSelectable={elementsSelectable}
selectNodesOnDrag={selectNodesOnDrag}
minZoom={minZoom}
maxZoom={maxZoom}
@@ -135,6 +154,7 @@ ReactFlow.defaultProps = {
default: BezierEdge,
straight: StraightEdge,
step: StepEdge,
+ smoothstep: SmoothStepEdge,
},
connectionLineType: ConnectionLineType.Bezier,
deleteKeyCode: 8,
@@ -142,7 +162,9 @@ ReactFlow.defaultProps = {
snapToGrid: false,
snapGrid: [16, 16],
onlyRenderVisibleNodes: true,
- isInteractive: true,
+ nodesDraggable: true,
+ nodesConnectable: true,
+ elementsSelectable: true,
selectNodesOnDrag: true,
minZoom: 0.5,
maxZoom: 2,
diff --git a/src/store/index.ts b/src/store/index.ts
index bb5486c2..5194a04b 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -79,7 +79,9 @@ export interface StoreModel {
snapToGrid: boolean;
snapGrid: [number, number];
- isInteractive: boolean;
+ nodesDraggable: boolean;
+ nodesConnectable: boolean;
+ elementsSelectable: boolean;
reactFlowVersion: string;
@@ -116,6 +118,9 @@ export interface StoreModel {
setConnectionNodeId: Action;
setInteractive: Action;
+ setNodesDraggable: Action;
+ setNodesConnectable: Action;
+ setElementsSelectable: Action;
setUserSelection: Action;
updateUserSelection: Action;
@@ -162,7 +167,9 @@ export const storeModel: StoreModel = {
snapGrid: [16, 16],
snapToGrid: false,
- isInteractive: true,
+ nodesDraggable: true,
+ nodesConnectable: true,
+ elementsSelectable: true,
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
@@ -373,7 +380,21 @@ export const storeModel: StoreModel = {
}),
setInteractive: action((state, isInteractive) => {
- state.isInteractive = isInteractive;
+ state.nodesDraggable = isInteractive;
+ state.nodesConnectable = isInteractive;
+ state.elementsSelectable = isInteractive;
+ }),
+
+ setNodesDraggable: action((state, nodesDraggable) => {
+ state.nodesDraggable = nodesDraggable;
+ }),
+
+ setNodesConnectable: action((state, nodesConnectable) => {
+ state.nodesConnectable = nodesConnectable;
+ }),
+
+ setElementsSelectable: action((state, elementsSelectable) => {
+ state.elementsSelectable = elementsSelectable;
}),
fitView: action((state, payload = { padding: 0.1 }) => {
diff --git a/src/style.css b/src/style.css
index 731a6191..e2057f62 100644
--- a/src/style.css
+++ b/src/style.css
@@ -112,7 +112,11 @@
font-size: 12px;
color: #222;
text-align: center;
+}
+.react-flow__node-default.selectable,
+.react-flow__node-input.selectable,
+.react-flow__node-output.selectable {
&.selected,
&.selected:hover {
box-shadow: 0 0 0 2px #555;
@@ -158,7 +162,12 @@
width: 10px;
height: 8px;
background: rgba(255, 255, 255, 0.4);
- cursor: crosshair;
+ pointer-events: none;
+
+ &.connectable {
+ pointer-events: all;
+ cursor: crosshair;
+ }
}
.react-flow__handle-bottom {
diff --git a/src/types/index.ts b/src/types/index.ts
index c82983a4..47a27e58 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,4 +1,4 @@
-import { CSSProperties } from 'react';
+import { CSSProperties, MouseEvent } from 'react';
export type ElementId = string;
@@ -94,6 +94,7 @@ export interface NodeProps {
type: string;
data: any;
selected: boolean;
+ isConnectable: boolean;
targetPosition?: Position;
sourcePosition?: Position;
}
@@ -102,6 +103,7 @@ export interface NodeComponentProps {
id: ElementId;
type: string;
data: any;
+ isConnectable: boolean;
selected?: boolean;
transform?: Transform;
xPos?: number;
@@ -109,6 +111,10 @@ export interface NodeComponentProps {
targetPosition?: Position;
sourcePosition?: Position;
onClick?: (node: Node) => void;
+ onMouseEnter?: (node: Node) => void;
+ onMouseMove?: (node: Node) => void;
+ onMouseLeave?: (node: Node) => void;
+ onContextMenu?: (node: Node) => void;
onNodeDragStart?: (node: Node) => void;
onNodeDragStop?: (node: Node) => void;
style?: CSSProperties;
@@ -122,9 +128,15 @@ export interface WrapNodeProps {
transform: Transform;
xPos: number;
yPos: number;
- isInteractive: boolean;
+ isSelectable: boolean;
+ isDraggable: boolean;
+ isConnectable: boolean;
selectNodesOnDrag: boolean;
onClick?: (node: Node) => void;
+ onMouseEnter?: (evt: MouseEvent, node: Node) => void;
+ onMouseMove?: (evt: MouseEvent, node: Node) => void;
+ onMouseLeave?: (evt: MouseEvent, node: Node) => void;
+ onContextMenu?: (evt: MouseEvent, node: Node) => void;
onNodeDragStart?: (node: Node) => void;
onNodeDragStop?: (node: Node) => void;
style?: CSSProperties;
@@ -155,8 +167,10 @@ export interface Connection {
}
export enum ConnectionLineType {
- Bezier = 'bezier',
+ Bezier = 'default',
Straight = 'straight',
+ Step = 'step',
+ SmoothStep = 'smoothstep',
}
export type OnConnectFunc = (connection: Connection) => void;
@@ -174,6 +188,7 @@ export interface HandleElement extends XYPosition, Dimensions {
export interface HandleProps {
type: HandleType;
position: Position;
+ isConnectable?: boolean;
onConnect?: OnConnectFunc;
isValidConnection?: (connection: Connection) => boolean;
id?: string;
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 74f6f3d6..17fab772 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,6 +1,8 @@
import { DraggableEvent } from 'react-draggable';
import { MouseEvent as ReactMouseEvent } from 'react';
+export const noop = () => {};
+
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
const target = e.target as HTMLElement;
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);