feat(nodes): add enter, move, leave and context handler #301
This commit is contained in:
@@ -69,6 +69,10 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
|
|||||||
- `onElementsRemove(elements: Elements)`: element remove callback
|
- `onElementsRemove(elements: Elements)`: element remove callback
|
||||||
- `onNodeDragStart(node: Node)`: node drag start callback
|
- `onNodeDragStart(node: Node)`: node drag start callback
|
||||||
- `onNodeDragStop(node: Node)`: node drag stop 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
|
- `onConnect({ source, target })`: connect callback
|
||||||
- `onLoad(reactFlowInstance)`: editor load callback
|
- `onLoad(reactFlowInstance)`: editor load callback
|
||||||
- `onMove()`: move callback
|
- `onMove()`: move callback
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import ReactFlow, { removeElements, addEdge } from 'react-flow-renderer';
|
|||||||
|
|
||||||
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
|
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 = [
|
const initialElements = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -21,13 +29,13 @@ const initialElements = [
|
|||||||
{ id: '7', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 7' }, position: { x: 750, y: 50 } },
|
{ 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: '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-2', source: '1', type: 'smoothstep', target: '2', animated: true },
|
||||||
{ id: 'e1-3', source: '1', target: '3', animated: true },
|
{ id: 'e1-3', source: '1', type: 'smoothstep', target: '3', animated: true },
|
||||||
{ id: 'e1-4', source: '2', target: '4', label: 'edge label' },
|
{ id: 'e1-4', source: '2', type: 'smoothstep', target: '4', label: 'edge label' },
|
||||||
{ id: 'e3-5', source: '3', target: '5', animated: true },
|
{ id: 'e3-5', source: '3', type: 'smoothstep', target: '5', animated: true },
|
||||||
{ id: 'e3-6', source: '3', target: '6', animated: true },
|
{ id: 'e3-6', source: '3', type: 'smoothstep', target: '6', animated: true },
|
||||||
{ id: 'e5-7', source: '5', target: '7', animated: true },
|
{ id: 'e5-7', source: '5', type: 'smoothstep', target: '7', animated: true },
|
||||||
{ id: 'e6-8', source: '6', target: '8', animated: true },
|
{ id: 'e6-8', source: '6', type: 'smoothstep', target: '8', animated: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const HorizontalFlow = () => {
|
const HorizontalFlow = () => {
|
||||||
@@ -53,6 +61,10 @@ const HorizontalFlow = () => {
|
|||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
selectNodesOnDrag={false}
|
selectNodesOnDrag={false}
|
||||||
|
onNodeMouseEnter={onNodeMouseEnter}
|
||||||
|
onNodeMouseMove={onNodeMouseMove}
|
||||||
|
onNodeMouseLeave={onNodeMouseLeave}
|
||||||
|
onNodeContextMenu={onNodeContextMenu}
|
||||||
>
|
>
|
||||||
<button onClick={changeClassName} style={{ position: 'absolute', right: 10, top: 30, zIndex: 4 }}>
|
<button onClick={changeClassName} style={{ position: 'absolute', right: 10, top: 30, zIndex: 4 }}>
|
||||||
change class name
|
change class name
|
||||||
|
|||||||
@@ -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 { DraggableCore } from 'react-draggable';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { ResizeObserver } from 'resize-observer';
|
import { ResizeObserver } from 'resize-observer';
|
||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
NodePosUpdate,
|
NodePosUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
|
import { noop } from '../../utils';
|
||||||
|
|
||||||
const getMouseEvent = (evt: MouseEvent | TouchEvent) =>
|
const getMouseEvent = (evt: MouseEvent | TouchEvent) =>
|
||||||
typeof TouchEvent !== 'undefined' && evt instanceof TouchEvent ? evt.touches[0] : (evt as MouseEvent);
|
typeof TouchEvent !== 'undefined' && evt instanceof TouchEvent ? evt.touches[0] : (evt as MouseEvent);
|
||||||
|
|
||||||
@@ -154,6 +156,10 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
yPos,
|
yPos,
|
||||||
selected,
|
selected,
|
||||||
onClick,
|
onClick,
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseMove,
|
||||||
|
onMouseLeave,
|
||||||
|
onContextMenu,
|
||||||
onNodeDragStart,
|
onNodeDragStart,
|
||||||
onNodeDragStop,
|
onNodeDragStop,
|
||||||
style,
|
style,
|
||||||
@@ -172,6 +178,38 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
const [isDragging, setDragging] = useState(false);
|
const [isDragging, setDragging] = useState(false);
|
||||||
const position = { x: xPos, y: yPos };
|
const position = { x: xPos, y: yPos };
|
||||||
const nodeClasses = cx('react-flow__node', `react-flow__node-${type}`, className, { selected });
|
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 = {
|
const nodeStyle: CSSProperties = {
|
||||||
zIndex: selected ? 10 : 3,
|
zIndex: selected ? 10 : 3,
|
||||||
@@ -237,7 +275,15 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
disabled={!isInteractive}
|
disabled={!isInteractive}
|
||||||
cancel=".nodrag"
|
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}>
|
<Provider value={id}>
|
||||||
<NodeComponent
|
<NodeComponent
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@@ -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 classnames from 'classnames';
|
||||||
import { ResizeObserver } from 'resize-observer';
|
import { ResizeObserver } from 'resize-observer';
|
||||||
|
|
||||||
@@ -28,6 +28,10 @@ export interface GraphViewProps {
|
|||||||
elements: Elements;
|
elements: Elements;
|
||||||
onElementClick?: (element: Node | Edge) => void;
|
onElementClick?: (element: Node | Edge) => void;
|
||||||
onElementsRemove?: (elements: Elements) => 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;
|
onNodeDragStart?: (node: Node) => void;
|
||||||
onNodeDragStop?: (node: Node) => void;
|
onNodeDragStop?: (node: Node) => void;
|
||||||
onConnect?: (connection: Connection | Edge) => void;
|
onConnect?: (connection: Connection | Edge) => void;
|
||||||
@@ -56,6 +60,10 @@ const GraphView = memo(
|
|||||||
onMove,
|
onMove,
|
||||||
onLoad,
|
onLoad,
|
||||||
onElementClick,
|
onElementClick,
|
||||||
|
onNodeMouseEnter,
|
||||||
|
onNodeMouseMove,
|
||||||
|
onNodeMouseLeave,
|
||||||
|
onNodeContextMenu,
|
||||||
onNodeDragStart,
|
onNodeDragStart,
|
||||||
onNodeDragStop,
|
onNodeDragStop,
|
||||||
connectionLineType,
|
connectionLineType,
|
||||||
@@ -176,6 +184,10 @@ const GraphView = memo(
|
|||||||
<NodeRenderer
|
<NodeRenderer
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
onElementClick={onElementClick}
|
onElementClick={onElementClick}
|
||||||
|
onNodeMouseEnter={onNodeMouseEnter}
|
||||||
|
onNodeMouseMove={onNodeMouseMove}
|
||||||
|
onNodeMouseLeave={onNodeMouseLeave}
|
||||||
|
onNodeContextMenu={onNodeContextMenu}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
onNodeDragStart={onNodeDragStart}
|
onNodeDragStart={onNodeDragStart}
|
||||||
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { memo, ComponentType } from 'react';
|
import React, { memo, ComponentType, MouseEvent } from 'react';
|
||||||
|
|
||||||
import { useStoreState } from '../../store/hooks';
|
import { useStoreState } from '../../store/hooks';
|
||||||
import { getNodesInside } from '../../utils/graph';
|
import { getNodesInside } from '../../utils/graph';
|
||||||
@@ -8,6 +8,10 @@ interface NodeRendererProps {
|
|||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
selectNodesOnDrag: boolean;
|
selectNodesOnDrag: boolean;
|
||||||
onElementClick?: (element: Node | Edge) => void;
|
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;
|
onNodeDragStart?: (node: Node) => void;
|
||||||
onNodeDragStop?: (node: Node) => void;
|
onNodeDragStop?: (node: Node) => void;
|
||||||
onlyRenderVisibleNodes?: boolean;
|
onlyRenderVisibleNodes?: boolean;
|
||||||
@@ -37,6 +41,10 @@ function renderNode(
|
|||||||
xPos={node.__rf.position.x}
|
xPos={node.__rf.position.x}
|
||||||
yPos={node.__rf.position.y}
|
yPos={node.__rf.position.y}
|
||||||
onClick={props.onElementClick}
|
onClick={props.onElementClick}
|
||||||
|
onMouseEnter={props.onNodeMouseEnter}
|
||||||
|
onMouseMove={props.onNodeMouseMove}
|
||||||
|
onMouseLeave={props.onNodeMouseLeave}
|
||||||
|
onContextMenu={props.onNodeContextMenu}
|
||||||
onNodeDragStart={props.onNodeDragStart}
|
onNodeDragStart={props.onNodeDragStart}
|
||||||
onNodeDragStop={props.onNodeDragStop}
|
onNodeDragStop={props.onNodeDragStop}
|
||||||
transform={transform}
|
transform={transform}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, CSSProperties, HTMLAttributes } from 'react';
|
import React, { useMemo, CSSProperties, HTMLAttributes, MouseEvent } from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
const nodeEnv: string = process.env.NODE_ENV as string;
|
const nodeEnv: string = process.env.NODE_ENV as string;
|
||||||
@@ -37,6 +37,10 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
elements: Elements;
|
elements: Elements;
|
||||||
onElementClick?: (element: Node | Edge) => void;
|
onElementClick?: (element: Node | Edge) => void;
|
||||||
onElementsRemove?: (elements: Elements) => 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;
|
onNodeDragStart?: (node: Node) => void;
|
||||||
onNodeDragStop?: (node: Node) => void;
|
onNodeDragStop?: (node: Node) => void;
|
||||||
onConnect?: (connection: Edge | Connection) => void;
|
onConnect?: (connection: Edge | Connection) => void;
|
||||||
@@ -71,6 +75,10 @@ const ReactFlow = ({
|
|||||||
onMove,
|
onMove,
|
||||||
onElementsRemove,
|
onElementsRemove,
|
||||||
onConnect,
|
onConnect,
|
||||||
|
onNodeMouseEnter,
|
||||||
|
onNodeMouseMove,
|
||||||
|
onNodeMouseLeave,
|
||||||
|
onNodeContextMenu,
|
||||||
onNodeDragStart,
|
onNodeDragStart,
|
||||||
onNodeDragStop,
|
onNodeDragStop,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
@@ -97,6 +105,10 @@ const ReactFlow = ({
|
|||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onMove={onMove}
|
onMove={onMove}
|
||||||
onElementClick={onElementClick}
|
onElementClick={onElementClick}
|
||||||
|
onNodeMouseEnter={onNodeMouseEnter}
|
||||||
|
onNodeMouseMove={onNodeMouseMove}
|
||||||
|
onNodeMouseLeave={onNodeMouseLeave}
|
||||||
|
onNodeContextMenu={onNodeContextMenu}
|
||||||
onNodeDragStart={onNodeDragStart}
|
onNodeDragStart={onNodeDragStart}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
nodeTypes={nodeTypesParsed}
|
nodeTypes={nodeTypesParsed}
|
||||||
|
|||||||
+9
-1
@@ -1,4 +1,4 @@
|
|||||||
import { CSSProperties } from 'react';
|
import { CSSProperties, MouseEvent } from 'react';
|
||||||
|
|
||||||
export type ElementId = string;
|
export type ElementId = string;
|
||||||
|
|
||||||
@@ -109,6 +109,10 @@ export interface NodeComponentProps {
|
|||||||
targetPosition?: Position;
|
targetPosition?: Position;
|
||||||
sourcePosition?: Position;
|
sourcePosition?: Position;
|
||||||
onClick?: (node: Node) => void;
|
onClick?: (node: Node) => void;
|
||||||
|
onMouseEnter?: (node: Node) => void;
|
||||||
|
onMouseMove?: (node: Node) => void;
|
||||||
|
onMouseLeave?: (node: Node) => void;
|
||||||
|
onContextMenu?: (node: Node) => void;
|
||||||
onNodeDragStart?: (node: Node) => void;
|
onNodeDragStart?: (node: Node) => void;
|
||||||
onNodeDragStop?: (node: Node) => void;
|
onNodeDragStop?: (node: Node) => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
@@ -125,6 +129,10 @@ export interface WrapNodeProps {
|
|||||||
isInteractive: boolean;
|
isInteractive: boolean;
|
||||||
selectNodesOnDrag: boolean;
|
selectNodesOnDrag: boolean;
|
||||||
onClick?: (node: Node) => void;
|
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;
|
onNodeDragStart?: (node: Node) => void;
|
||||||
onNodeDragStop?: (node: Node) => void;
|
onNodeDragStop?: (node: Node) => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { DraggableEvent } from 'react-draggable';
|
import { DraggableEvent } from 'react-draggable';
|
||||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
|
|
||||||
|
export const noop = () => {};
|
||||||
|
|
||||||
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
|
export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
|
return e && target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName);
|
||||||
|
|||||||
Reference in New Issue
Block a user