feat(nodes): add enter, move, leave and context handler #301

This commit is contained in:
moklick
2020-07-13 16:04:49 +02:00
parent b32d5454e1
commit 32f6101d37
8 changed files with 117 additions and 13 deletions
+4
View File
@@ -69,6 +69,10 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `onElementsRemove(elements: Elements)`: element remove callback
- `onNodeDragStart(node: Node)`: node drag start callback
- `onNodeDragStop(node: Node)`: node drag stop callback
- `onNodeMouseEnter(evt: MouseEvent, node: Node)`: node mouse enter callback
- `onNodeMouseMove(evt: MouseEvent, node: Node)`: node mouse move callback
- `onNodeMouseLeave(evt: MouseEvent, node: Node)`: node mouse leave callback
- `onNodeContextMenu(evt: MouseEvent, node: Node)`: node context menu callback
- `onConnect({ source, target })`: connect callback
- `onLoad(reactFlowInstance)`: editor load callback
- `onMove()`: move callback
+19 -7
View File
@@ -4,6 +4,14 @@ import ReactFlow, { removeElements, addEdge } from 'react-flow-renderer';
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
const onNodeMouseEnter = (evt, node) => console.log('mouse enter:', node);
const onNodeMouseMove = (evt, node) => console.log('mouse move:', node);
const onNodeMouseLeave = (evt, node) => console.log('mouse leave:', node);
const onNodeContextMenu = (evt, node) => {
evt.preventDefault();
console.log('context menu:', node);
};
const initialElements = [
{
id: '1',
@@ -21,13 +29,13 @@ const initialElements = [
{ id: '7', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 7' }, position: { x: 750, y: 50 } },
{ id: '8', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 8' }, position: { x: 750, y: 300 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3', animated: true },
{ id: 'e1-4', source: '2', target: '4', label: 'edge label' },
{ id: 'e3-5', source: '3', target: '5', animated: true },
{ id: 'e3-6', source: '3', target: '6', animated: true },
{ id: 'e5-7', source: '5', target: '7', animated: true },
{ id: 'e6-8', source: '6', target: '8', animated: true },
{ id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true },
{ id: 'e1-3', source: '1', type: 'smoothstep', target: '3', animated: true },
{ id: 'e1-4', source: '2', type: 'smoothstep', target: '4', label: 'edge label' },
{ id: 'e3-5', source: '3', type: 'smoothstep', target: '5', animated: true },
{ id: 'e3-6', source: '3', type: 'smoothstep', target: '6', animated: true },
{ id: 'e5-7', source: '5', type: 'smoothstep', target: '7', animated: true },
{ id: 'e6-8', source: '6', type: 'smoothstep', target: '8', animated: true },
];
const HorizontalFlow = () => {
@@ -53,6 +61,10 @@ const HorizontalFlow = () => {
onConnect={onConnect}
onLoad={onLoad}
selectNodesOnDrag={false}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu}
>
<button onClick={changeClassName} style={{ position: 'absolute', right: 10, top: 30, zIndex: 4 }}>
change class name
+48 -2
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState, memo, ComponentType, CSSProperties } from 'react';
import React, { useEffect, useRef, useState, memo, ComponentType, CSSProperties, useMemo, MouseEvent } from 'react';
import { DraggableCore } from 'react-draggable';
import cx from 'classnames';
import { ResizeObserver } from 'resize-observer';
@@ -17,6 +17,8 @@ import {
NodePosUpdate,
} from '../../types';
import { noop } from '../../utils';
const getMouseEvent = (evt: MouseEvent | TouchEvent) =>
typeof TouchEvent !== 'undefined' && evt instanceof TouchEvent ? evt.touches[0] : (evt as MouseEvent);
@@ -154,6 +156,10 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
yPos,
selected,
onClick,
onMouseEnter,
onMouseMove,
onMouseLeave,
onContextMenu,
onNodeDragStart,
onNodeDragStop,
style,
@@ -172,6 +178,38 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos };
const nodeClasses = cx('react-flow__node', `react-flow__node-${type}`, className, { selected });
const node = { id, type, position, data };
const onMouseEnterHandler = useMemo(() => {
if (!onMouseEnter || isDragging) {
return noop;
}
return (evt: MouseEvent) => onMouseEnter(evt, node);
}, [onMouseEnter, isDragging]);
const onMouseMoveHandler = useMemo(() => {
if (!onMouseMove || isDragging) {
return noop;
}
return (evt: MouseEvent) => onMouseMove(evt, node);
}, [onMouseMove, isDragging]);
const onMouseLeaveHandler = useMemo(() => {
if (!onMouseLeave || isDragging) {
return noop;
}
return (evt: MouseEvent) => onMouseLeave(evt, node);
}, [onMouseLeave, isDragging]);
const onContextMenuHandler = useMemo(() => {
if (!onContextMenu) {
return noop;
}
return (evt: MouseEvent) => onContextMenu(evt, node);
}, [onContextMenu]);
const nodeStyle: CSSProperties = {
zIndex: selected ? 10 : 3,
@@ -237,7 +275,15 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
disabled={!isInteractive}
cancel=".nodrag"
>
<div className={nodeClasses} ref={nodeElement} style={nodeStyle}>
<div
className={nodeClasses}
ref={nodeElement}
style={nodeStyle}
onMouseEnter={onMouseEnterHandler}
onMouseMove={onMouseMoveHandler}
onMouseLeave={onMouseLeaveHandler}
onContextMenu={onContextMenuHandler}
>
<Provider value={id}>
<NodeComponent
id={id}
+13 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, memo, CSSProperties } from 'react';
import React, { useEffect, useRef, memo, CSSProperties, MouseEvent } from 'react';
import classnames from 'classnames';
import { ResizeObserver } from 'resize-observer';
@@ -28,6 +28,10 @@ export interface GraphViewProps {
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: Connection | Edge) => void;
@@ -56,6 +60,10 @@ const GraphView = memo(
onMove,
onLoad,
onElementClick,
onNodeMouseEnter,
onNodeMouseMove,
onNodeMouseLeave,
onNodeContextMenu,
onNodeDragStart,
onNodeDragStop,
connectionLineType,
@@ -176,6 +184,10 @@ const GraphView = memo(
<NodeRenderer
nodeTypes={nodeTypes}
onElementClick={onElementClick}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu}
onNodeDragStop={onNodeDragStop}
onNodeDragStart={onNodeDragStart}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
+9 -1
View File
@@ -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;
@@ -37,6 +41,10 @@ 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}
+13 -1
View File
@@ -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;
@@ -37,6 +37,10 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, '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;
@@ -71,6 +75,10 @@ const ReactFlow = ({
onMove,
onElementsRemove,
onConnect,
onNodeMouseEnter,
onNodeMouseMove,
onNodeMouseLeave,
onNodeContextMenu,
onNodeDragStart,
onNodeDragStop,
onSelectionChange,
@@ -97,6 +105,10 @@ const ReactFlow = ({
onLoad={onLoad}
onMove={onMove}
onElementClick={onElementClick}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
nodeTypes={nodeTypesParsed}
+9 -1
View File
@@ -1,4 +1,4 @@
import { CSSProperties } from 'react';
import { CSSProperties, MouseEvent } from 'react';
export type ElementId = string;
@@ -109,6 +109,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;
@@ -125,6 +129,10 @@ export interface WrapNodeProps {
isInteractive: 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;
+2
View File
@@ -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);