refactor(useDragNode): cleanup, refactoring
This commit is contained in:
@@ -15,9 +15,9 @@ import ReactFlow, {
|
|||||||
OnSelectionChangeParams,
|
OnSelectionChangeParams,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
const onNodeDragStart = (_: ReactMouseEvent, node: Node) => console.log('drag start', node);
|
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
||||||
const onNodeDrag = (_: ReactMouseEvent, node: Node) => console.log('drag', node);
|
const onNodeDrag = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
|
||||||
const onNodeDragStop = (_: ReactMouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
|
||||||
const onNodeDoubleClick = (_: ReactMouseEvent, node: Node) => console.log('node double click', node);
|
const onNodeDoubleClick = (_: ReactMouseEvent, node: Node) => console.log('node double click', node);
|
||||||
const onPaneClick = (event: ReactMouseEvent) => console.log('pane click', event);
|
const onPaneClick = (event: ReactMouseEvent) => console.log('pane click', event);
|
||||||
const onPaneScroll = (event?: ReactMouseEvent) => console.log('pane scroll', event);
|
const onPaneScroll = (event?: ReactMouseEvent) => console.log('pane scroll', event);
|
||||||
@@ -50,6 +50,7 @@ const initialNodes: Node[] = [
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
draggable: false,
|
||||||
data: {
|
data: {
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
import React, {
|
import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react';
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
memo,
|
|
||||||
ComponentType,
|
|
||||||
CSSProperties,
|
|
||||||
useMemo,
|
|
||||||
MouseEvent,
|
|
||||||
useCallback,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
@@ -16,12 +6,10 @@ import { useStore, useStoreApi } from '../../store';
|
|||||||
import { Provider } from '../../contexts/NodeIdContext';
|
import { Provider } from '../../contexts/NodeIdContext';
|
||||||
import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
|
import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
|
||||||
import useMemoizedMouseHandler from './useMemoizedMouseHandler';
|
import useMemoizedMouseHandler from './useMemoizedMouseHandler';
|
||||||
import useDrag, { UseDragEvent, UseDragData } from '../../hooks/useDrag';
|
import useDragNode from '../../hooks/useDragNode';
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
addSelectedNodes: s.addSelectedNodes,
|
addSelectedNodes: s.addSelectedNodes,
|
||||||
updateNodePosition: s.updateNodePosition,
|
|
||||||
unselectNodesAndEdges: s.unselectNodesAndEdges,
|
|
||||||
updateNodeDimensions: s.updateNodeDimensions,
|
updateNodeDimensions: s.updateNodeDimensions,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,12 +46,8 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
noPanClassName,
|
noPanClassName,
|
||||||
noDragClassName,
|
noDragClassName,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
const [dragging, setDragging] = useState(false);
|
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { addSelectedNodes, unselectNodesAndEdges, updateNodePosition, updateNodeDimensions } = useStore(
|
const { addSelectedNodes, updateNodeDimensions } = useStore(selector, shallow);
|
||||||
selector,
|
|
||||||
shallow
|
|
||||||
);
|
|
||||||
const nodeElement = useRef<HTMLDivElement>(null);
|
const nodeElement = useRef<HTMLDivElement>(null);
|
||||||
const prevSourcePosition = useRef(sourcePosition);
|
const prevSourcePosition = useRef(sourcePosition);
|
||||||
const prevTargetPosition = useRef(targetPosition);
|
const prevTargetPosition = useRef(targetPosition);
|
||||||
@@ -103,67 +87,6 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
[isSelectable, selected, onClick, id]
|
[isSelectable, selected, onClick, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDragStart = useCallback(
|
|
||||||
(event: UseDragEvent) => {
|
|
||||||
if (selectNodesOnDrag && isSelectable) {
|
|
||||||
store.setState({ nodesSelectionActive: false });
|
|
||||||
|
|
||||||
if (!selected) {
|
|
||||||
addSelectedNodes([id]);
|
|
||||||
}
|
|
||||||
} else if (!selectNodesOnDrag && !selected && isSelectable) {
|
|
||||||
const { multiSelectionActive } = store.getState();
|
|
||||||
if (multiSelectionActive) {
|
|
||||||
addSelectedNodes([id]);
|
|
||||||
} else {
|
|
||||||
unselectNodesAndEdges();
|
|
||||||
store.setState({ nodesSelectionActive: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onNodeDragStart) {
|
|
||||||
const node = store.getState().nodeInternals.get(id)!;
|
|
||||||
onNodeDragStart(event.sourceEvent as MouseEvent, { ...node });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[id, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDrag = useCallback(
|
|
||||||
(event: UseDragEvent, dragPos: UseDragData) => {
|
|
||||||
updateNodePosition({ id, diff: { x: dragPos.dx, y: dragPos.dy } });
|
|
||||||
setDragging(true);
|
|
||||||
|
|
||||||
if (onNodeDrag) {
|
|
||||||
const node = store.getState().nodeInternals.get(id)!;
|
|
||||||
onNodeDrag(event.sourceEvent as MouseEvent, {
|
|
||||||
...node,
|
|
||||||
position: {
|
|
||||||
x: node.position.x + dragPos.dx,
|
|
||||||
y: node.position.y + dragPos.dy,
|
|
||||||
},
|
|
||||||
positionAbsolute: {
|
|
||||||
x: (node.positionAbsolute?.x || 0) + dragPos.dx,
|
|
||||||
y: (node.positionAbsolute?.y || 0) + dragPos.dy,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[id, onNodeDrag]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDragStop = useCallback(
|
|
||||||
(event: UseDragEvent) => {
|
|
||||||
|
|
||||||
setDragging(false);
|
|
||||||
if (onNodeDragStop) {
|
|
||||||
const node = store.getState().nodeInternals.get(id)!;
|
|
||||||
onNodeDragStop(event.sourceEvent as MouseEvent, { ...node });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[id, onNodeDragStop]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeElement.current && !hidden) {
|
if (nodeElement.current && !hidden) {
|
||||||
const currNode = nodeElement.current;
|
const currNode = nodeElement.current;
|
||||||
@@ -193,15 +116,17 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
}
|
}
|
||||||
}, [id, type, sourcePosition, targetPosition]);
|
}, [id, type, sourcePosition, targetPosition]);
|
||||||
|
|
||||||
useDrag({
|
const dragging = useDragNode({
|
||||||
onStart: onDragStart,
|
onStart: onNodeDragStart,
|
||||||
onDrag: onDrag,
|
onDrag: onNodeDrag,
|
||||||
onStop: onDragStop,
|
onStop: onNodeDragStop,
|
||||||
nodeRef: nodeElement,
|
nodeRef: nodeElement,
|
||||||
disabled: !isDraggable,
|
disabled: !isDraggable,
|
||||||
noDragClassName,
|
noDragClassName,
|
||||||
handleSelector: dragHandle,
|
handleSelector: dragHandle,
|
||||||
nodeId: id,
|
nodeId: id,
|
||||||
|
isSelectable,
|
||||||
|
selectNodesOnDrag,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import shallow from 'zustand/shallow';
|
|||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { Node, ReactFlowState } from '../../types';
|
import { Node, ReactFlowState } from '../../types';
|
||||||
import { getRectOfNodes } from '../../utils/graph';
|
import { getRectOfNodes } from '../../utils/graph';
|
||||||
import useDrag, { UseDragData, UseDragEvent } from '../../hooks/useDrag';
|
import useDragNode from '../../hooks/useDragNode';
|
||||||
|
|
||||||
export interface NodesSelectionProps {
|
export interface NodesSelectionProps {
|
||||||
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
|
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
|
||||||
@@ -19,13 +19,11 @@ export interface NodesSelectionProps {
|
|||||||
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
|
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
|
||||||
noPanClassName?: string;
|
noPanClassName?: string;
|
||||||
}
|
}
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
transform: s.transform,
|
transform: s.transform,
|
||||||
selectedNodesBbox: s.selectedNodesBbox,
|
|
||||||
userSelectionActive: s.userSelectionActive,
|
userSelectionActive: s.userSelectionActive,
|
||||||
selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected),
|
selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected),
|
||||||
updateNodePosition: s.updateNodePosition,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function NodesSelection({
|
function NodesSelection({
|
||||||
@@ -35,65 +33,32 @@ function NodesSelection({
|
|||||||
onSelectionContextMenu,
|
onSelectionContextMenu,
|
||||||
noPanClassName,
|
noPanClassName,
|
||||||
}: NodesSelectionProps) {
|
}: NodesSelectionProps) {
|
||||||
const { transform, userSelectionActive, selectedNodes, updateNodePosition } = useStore(selector, shallow);
|
const { transform, userSelectionActive, selectedNodes } = useStore(selector, shallow);
|
||||||
const [tX, tY, tScale] = transform;
|
const [tX, tY, tScale] = transform;
|
||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
|
|
||||||
const style = useMemo(
|
|
||||||
() => ({
|
|
||||||
transform: `translate(${tX}px,${tY}px) scale(${tScale})`,
|
|
||||||
}),
|
|
||||||
[tX, tY, tScale]
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedNodesBbox = useMemo(() => getRectOfNodes(selectedNodes), [selectedNodes]);
|
const selectedNodesBbox = useMemo(() => getRectOfNodes(selectedNodes), [selectedNodes]);
|
||||||
|
|
||||||
const innerStyle = useMemo(
|
|
||||||
() => ({
|
|
||||||
width: selectedNodesBbox.width,
|
|
||||||
height: selectedNodesBbox.height,
|
|
||||||
top: selectedNodesBbox.y,
|
|
||||||
left: selectedNodesBbox.x,
|
|
||||||
}),
|
|
||||||
[selectedNodesBbox]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onStart = useCallback(
|
const onStart = useCallback(
|
||||||
(event: UseDragEvent) => {
|
(event: MouseEvent, _: Node, nodes: Node[]) => onSelectionDragStart?.(event, nodes),
|
||||||
onSelectionDragStart?.(event.sourceEvent, selectedNodes);
|
[onSelectionDragStart]
|
||||||
},
|
|
||||||
[onSelectionDragStart, selectedNodes]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrag = useCallback(
|
const onDrag = useCallback(
|
||||||
(event: UseDragEvent, data: UseDragData) => {
|
(event: MouseEvent, _: Node, nodes: Node[]) => onSelectionDrag?.(event, nodes),
|
||||||
updateNodePosition({
|
[onSelectionDrag]
|
||||||
diff: {
|
|
||||||
x: data.dx,
|
|
||||||
y: data.dy,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
onSelectionDrag?.(event.sourceEvent, selectedNodes);
|
|
||||||
},
|
|
||||||
[onSelectionDrag, selectedNodes, updateNodePosition]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const onStop = useCallback(
|
const onStop = useCallback(
|
||||||
(event: UseDragEvent) => {
|
(event: MouseEvent, _: Node, nodes: Node[]) => onSelectionDragStop?.(event, nodes),
|
||||||
onSelectionDragStop?.(event.sourceEvent, selectedNodes);
|
[onSelectionDragStop]
|
||||||
},
|
|
||||||
[selectedNodes, onSelectionDragStop]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const onContextMenu = useCallback(
|
const onContextMenu = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => onSelectionContextMenu?.(event, selectedNodes),
|
||||||
onSelectionContextMenu?.(event, selectedNodes);
|
|
||||||
},
|
|
||||||
[onSelectionContextMenu, selectedNodes]
|
[onSelectionContextMenu, selectedNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
useDrag({
|
useDragNode({
|
||||||
onStart,
|
onStart,
|
||||||
onDrag,
|
onDrag,
|
||||||
onStop,
|
onStop,
|
||||||
@@ -105,8 +70,23 @@ function NodesSelection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName])} style={style}>
|
<div
|
||||||
<div ref={nodeRef} className="react-flow__nodesselection-rect" onContextMenu={onContextMenu} style={innerStyle} />
|
className={cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName])}
|
||||||
|
style={{
|
||||||
|
transform: `translate(${tX}px,${tY}px) scale(${tScale})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={nodeRef}
|
||||||
|
className="react-flow__nodesselection-rect"
|
||||||
|
onContextMenu={onContextMenu}
|
||||||
|
style={{
|
||||||
|
width: selectedNodesBbox.width,
|
||||||
|
height: selectedNodesBbox.height,
|
||||||
|
top: selectedNodesBbox.y,
|
||||||
|
left: selectedNodesBbox.x,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
import React, { memo, useMemo, ComponentType, MouseEvent, useEffect, useRef } from 'react';
|
import React, { memo, useMemo, ComponentType, useEffect, useRef } from 'react';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
import useVisibleNodes from '../../hooks/useVisibleNodes';
|
import useVisibleNodes from '../../hooks/useVisibleNodes';
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { Node, NodeTypesWrapped, Position, ReactFlowState, WrapNodeProps } from '../../types';
|
import {
|
||||||
|
NodeDragHandler,
|
||||||
|
NodeMouseHandler,
|
||||||
|
NodeTypesWrapped,
|
||||||
|
Position,
|
||||||
|
ReactFlowState,
|
||||||
|
WrapNodeProps,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
interface NodeRendererProps {
|
interface NodeRendererProps {
|
||||||
nodeTypes: NodeTypesWrapped;
|
nodeTypes: NodeTypesWrapped;
|
||||||
selectNodesOnDrag: boolean;
|
selectNodesOnDrag: boolean;
|
||||||
onNodeClick?: (event: MouseEvent, element: Node) => void;
|
onNodeClick?: NodeMouseHandler;
|
||||||
onNodeDoubleClick?: (event: MouseEvent, element: Node) => void;
|
onNodeDoubleClick?: NodeMouseHandler;
|
||||||
onNodeMouseEnter?: (event: MouseEvent, node: Node) => void;
|
onNodeMouseEnter?: NodeMouseHandler;
|
||||||
onNodeMouseMove?: (event: MouseEvent, node: Node) => void;
|
onNodeMouseMove?: NodeMouseHandler;
|
||||||
onNodeMouseLeave?: (event: MouseEvent, node: Node) => void;
|
onNodeMouseLeave?: NodeMouseHandler;
|
||||||
onNodeContextMenu?: (event: MouseEvent, node: Node) => void;
|
onNodeContextMenu?: NodeMouseHandler;
|
||||||
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
onNodeDragStart?: NodeDragHandler;
|
||||||
onNodeDrag?: (event: MouseEvent, node: Node) => void;
|
onNodeDrag?: NodeDragHandler;
|
||||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
onNodeDragStop?: NodeDragHandler;
|
||||||
onlyRenderVisibleElements: boolean;
|
onlyRenderVisibleElements: boolean;
|
||||||
noPanClassName: string;
|
noPanClassName: string;
|
||||||
noDragClassName: string;
|
noDragClassName: string;
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
import { RefObject, useEffect, useRef } from 'react';
|
|
||||||
import { D3DragEvent, drag, SubjectPosition } from 'd3-drag';
|
|
||||||
import { select } from 'd3-selection';
|
|
||||||
|
|
||||||
import { useStoreApi } from '../store';
|
|
||||||
import { pointToRendererPoint } from '../utils/graph';
|
|
||||||
import { NodeInternals, XYPosition } from '../types';
|
|
||||||
|
|
||||||
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
|
||||||
export type UseDragData = { dx: number; dy: number };
|
|
||||||
|
|
||||||
type UseDragParams = {
|
|
||||||
onStart: (event: UseDragEvent) => void;
|
|
||||||
onDrag: (event: UseDragEvent, data: UseDragData) => void;
|
|
||||||
onStop: (event: UseDragEvent) => void;
|
|
||||||
nodeRef: RefObject<Element>;
|
|
||||||
disabled?: boolean;
|
|
||||||
noDragClassName?: string;
|
|
||||||
handleSelector?: string;
|
|
||||||
nodeId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getOffset(event: UseDragEvent, nodeRef: RefObject<Element>): XYPosition {
|
|
||||||
const bounds = nodeRef.current?.getBoundingClientRect() || { x: 0, y: 0 };
|
|
||||||
const parent = (nodeRef.current as HTMLDivElement)?.offsetParent;
|
|
||||||
const parentBounds = parent?.getBoundingClientRect() || { x: 0, y: 0 };
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: event.x - (bounds.x - parentBounds.x - (parent?.scrollLeft || 0)),
|
|
||||||
y: event.y - (bounds.y - parentBounds.y - (parent?.scrollTop || 0)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParentNodePosition(nodeInternals: NodeInternals, nodeId?: string): XYPosition {
|
|
||||||
const parentNodeId = nodeId ? nodeInternals.get(nodeId)?.parentNode : null;
|
|
||||||
const parentNode = parentNodeId ? nodeInternals.get(parentNodeId) : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: parentNode?.positionAbsolute?.x || 0,
|
|
||||||
y: parentNode?.positionAbsolute?.y || 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectorExistsTargetToNode(target: Element, selector: string, nodeRef: RefObject<Element>): boolean {
|
|
||||||
let current = target;
|
|
||||||
do {
|
|
||||||
if (current?.matches(selector)) return true;
|
|
||||||
if (current === nodeRef.current) return false;
|
|
||||||
current = current.parentElement as Element;
|
|
||||||
} while (current);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDrag({
|
|
||||||
onStart,
|
|
||||||
onDrag,
|
|
||||||
onStop,
|
|
||||||
nodeRef,
|
|
||||||
disabled = false,
|
|
||||||
noDragClassName,
|
|
||||||
handleSelector,
|
|
||||||
nodeId,
|
|
||||||
}: UseDragParams) {
|
|
||||||
const store = useStoreApi();
|
|
||||||
const startPos = useRef<XYPosition>({ x: 0, y: 0 });
|
|
||||||
const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null });
|
|
||||||
const parentPos = useRef<XYPosition>({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (nodeRef?.current) {
|
|
||||||
const selection = select(nodeRef.current);
|
|
||||||
|
|
||||||
if (disabled) {
|
|
||||||
selection.on('.drag', null);
|
|
||||||
} else {
|
|
||||||
const dragHandler = drag()
|
|
||||||
.on('start', (event: UseDragEvent) => {
|
|
||||||
const { transform, nodeInternals } = store.getState();
|
|
||||||
const offset = getOffset(event, nodeRef);
|
|
||||||
parentPos.current = getParentNodePosition(nodeInternals, nodeId);
|
|
||||||
|
|
||||||
startPos.current = {
|
|
||||||
x: offset.x - transform[0],
|
|
||||||
y: offset.y - transform[1],
|
|
||||||
};
|
|
||||||
|
|
||||||
onStart(event);
|
|
||||||
})
|
|
||||||
.on('drag', (event: UseDragEvent) => {
|
|
||||||
const { transform, snapGrid, snapToGrid } = store.getState();
|
|
||||||
const pos = pointToRendererPoint(
|
|
||||||
{
|
|
||||||
x: event.x - startPos.current.x,
|
|
||||||
y: event.y - startPos.current.y,
|
|
||||||
},
|
|
||||||
transform,
|
|
||||||
snapToGrid,
|
|
||||||
snapGrid
|
|
||||||
);
|
|
||||||
|
|
||||||
pos.x -= parentPos.current.x;
|
|
||||||
pos.y -= parentPos.current.y;
|
|
||||||
|
|
||||||
// skip events without movement
|
|
||||||
if (lastPos.current.x !== pos.x || lastPos.current.y !== pos.y) {
|
|
||||||
lastPos.current = pos;
|
|
||||||
|
|
||||||
onDrag(event, {
|
|
||||||
dx: pos.x,
|
|
||||||
dy: pos.y,
|
|
||||||
});
|
|
||||||
|
|
||||||
event.on('end', (event) => {
|
|
||||||
onStop(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((event: any) => {
|
|
||||||
const filter = !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName);
|
|
||||||
return handleSelector
|
|
||||||
? selectorExistsTargetToNode(event.target, handleSelector, nodeRef) && filter
|
|
||||||
: filter;
|
|
||||||
});
|
|
||||||
|
|
||||||
selection.call(dragHandler);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
selection.on('.drag', null);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [onStart, onDrag, onStop, nodeRef, disabled, noDragClassName, handleSelector, nodeId]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useDrag;
|
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { RefObject, useEffect, useRef, MouseEvent, useState, useMemo, useCallback } from 'react';
|
||||||
|
import { D3DragEvent, drag, SubjectPosition } from 'd3-drag';
|
||||||
|
import { select } from 'd3-selection';
|
||||||
|
|
||||||
|
import { useStoreApi } from '../../store';
|
||||||
|
import { pointToRendererPoint } from '../../utils/graph';
|
||||||
|
import { NodeDragItem, NodeDragHandler, XYPosition } from '../../types';
|
||||||
|
import {
|
||||||
|
getDragItems,
|
||||||
|
getEventHandlerParams,
|
||||||
|
getParentNodePosition,
|
||||||
|
selectorExistsTargetToNode,
|
||||||
|
updatePosition,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
||||||
|
export type UseDragData = { dx: number; dy: number };
|
||||||
|
|
||||||
|
type UseDragParams = {
|
||||||
|
nodeRef: RefObject<Element>;
|
||||||
|
onStart?: NodeDragHandler;
|
||||||
|
onDrag?: NodeDragHandler;
|
||||||
|
onStop?: NodeDragHandler;
|
||||||
|
disabled?: boolean;
|
||||||
|
noDragClassName?: string;
|
||||||
|
handleSelector?: string;
|
||||||
|
nodeId?: string;
|
||||||
|
isSelectable?: boolean;
|
||||||
|
selectNodesOnDrag?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function useDrag({
|
||||||
|
onStart,
|
||||||
|
onDrag,
|
||||||
|
onStop,
|
||||||
|
nodeRef,
|
||||||
|
disabled = false,
|
||||||
|
noDragClassName,
|
||||||
|
handleSelector,
|
||||||
|
nodeId,
|
||||||
|
isSelectable,
|
||||||
|
selectNodesOnDrag,
|
||||||
|
}: UseDragParams) {
|
||||||
|
const [dragging, setDragging] = useState<boolean>(false);
|
||||||
|
const store = useStoreApi();
|
||||||
|
const dragItems = useRef<NodeDragItem[]>();
|
||||||
|
const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null });
|
||||||
|
const parentPos = useRef<XYPosition>({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
// TODO: should we store the ref or the bounds in the store?
|
||||||
|
// So that it is easier / more reliable to access?
|
||||||
|
const countainerBounds = useMemo(() => {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
return document.querySelector('.react-flow')?.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// returns the mouse position projected to the RF coordinate system
|
||||||
|
const getMousePosition = useCallback(
|
||||||
|
(event: UseDragEvent) => {
|
||||||
|
const { transform, snapGrid, snapToGrid } = store.getState();
|
||||||
|
|
||||||
|
const mousePos = pointToRendererPoint(
|
||||||
|
{
|
||||||
|
x: event.sourceEvent.clientX - (countainerBounds?.x || 0) - (window.scrollX || 0),
|
||||||
|
y: event.sourceEvent.clientY - (countainerBounds?.y || 0) - (window.scrollY || 0),
|
||||||
|
},
|
||||||
|
transform,
|
||||||
|
snapToGrid,
|
||||||
|
snapGrid
|
||||||
|
);
|
||||||
|
|
||||||
|
mousePos.x -= parentPos.current.x;
|
||||||
|
mousePos.y -= parentPos.current.y;
|
||||||
|
|
||||||
|
return mousePos;
|
||||||
|
},
|
||||||
|
[countainerBounds?.x, countainerBounds?.y, store]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (nodeRef?.current && countainerBounds) {
|
||||||
|
const selection = select(nodeRef.current);
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
selection.on('.drag', null);
|
||||||
|
} else {
|
||||||
|
const dragHandler = drag()
|
||||||
|
.on('start', (event: UseDragEvent) => {
|
||||||
|
const { nodeInternals, addSelectedNodes, unselectNodesAndEdges, multiSelectionActive } = store.getState();
|
||||||
|
parentPos.current = getParentNodePosition(nodeInternals, nodeId);
|
||||||
|
|
||||||
|
// this part is the regular drag handler for a single node
|
||||||
|
// it selects the dragged node and deselects all other nodes if multiSelectionActive = false
|
||||||
|
if (nodeId && isSelectable) {
|
||||||
|
const node = nodeInternals.get(nodeId)!;
|
||||||
|
|
||||||
|
if (selectNodesOnDrag) {
|
||||||
|
store.setState({ nodesSelectionActive: false });
|
||||||
|
|
||||||
|
if (!node.selected) {
|
||||||
|
addSelectedNodes([nodeId]);
|
||||||
|
}
|
||||||
|
} else if (!selectNodesOnDrag && !node.selected) {
|
||||||
|
if (multiSelectionActive) {
|
||||||
|
addSelectedNodes([nodeId]);
|
||||||
|
} else {
|
||||||
|
unselectNodesAndEdges();
|
||||||
|
store.setState({ nodesSelectionActive: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mousePos = getMousePosition(event);
|
||||||
|
dragItems.current = getDragItems(nodeInternals, mousePos);
|
||||||
|
|
||||||
|
if (onStart && dragItems.current) {
|
||||||
|
const [currentNode, nodes] = getEventHandlerParams({
|
||||||
|
nodeId,
|
||||||
|
dragItems: dragItems.current,
|
||||||
|
nodeInternals,
|
||||||
|
});
|
||||||
|
onStart(event.sourceEvent as MouseEvent, currentNode, nodes);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('drag', (event: UseDragEvent) => {
|
||||||
|
const { updateNodePositions, nodeInternals, nodeExtent } = store.getState();
|
||||||
|
const mousePos = getMousePosition(event);
|
||||||
|
|
||||||
|
// skip events without movement
|
||||||
|
if ((lastPos.current.x !== mousePos.x || lastPos.current.y !== mousePos.y) && dragItems.current) {
|
||||||
|
lastPos.current = mousePos;
|
||||||
|
dragItems.current = dragItems.current.map((n) => updatePosition(n, mousePos, nodeInternals, nodeExtent));
|
||||||
|
|
||||||
|
updateNodePositions(dragItems.current);
|
||||||
|
setDragging(true);
|
||||||
|
|
||||||
|
if (onDrag) {
|
||||||
|
const [currentNode, nodes] = getEventHandlerParams({
|
||||||
|
nodeId,
|
||||||
|
dragItems: dragItems.current,
|
||||||
|
nodeInternals,
|
||||||
|
});
|
||||||
|
onDrag(event.sourceEvent as MouseEvent, currentNode, nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.on('end', (event) => {
|
||||||
|
if (onStop && dragItems.current) {
|
||||||
|
const [currentNode, nodes] = getEventHandlerParams({
|
||||||
|
nodeId,
|
||||||
|
dragItems: dragItems.current,
|
||||||
|
nodeInternals,
|
||||||
|
});
|
||||||
|
onStop(event.sourceEvent as MouseEvent, currentNode, nodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.filter((event: any) => {
|
||||||
|
const filter = !event.ctrlKey && !event.button && !event.target.className.includes(noDragClassName);
|
||||||
|
return handleSelector
|
||||||
|
? selectorExistsTargetToNode(event.target, handleSelector, nodeRef) && filter
|
||||||
|
: filter;
|
||||||
|
});
|
||||||
|
|
||||||
|
selection.call(dragHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
selection.on('.drag', null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
onStart,
|
||||||
|
onDrag,
|
||||||
|
onStop,
|
||||||
|
nodeRef,
|
||||||
|
disabled,
|
||||||
|
noDragClassName,
|
||||||
|
handleSelector,
|
||||||
|
isSelectable,
|
||||||
|
store,
|
||||||
|
nodeId,
|
||||||
|
selectNodesOnDrag,
|
||||||
|
countainerBounds,
|
||||||
|
getMousePosition,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return dragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useDrag;
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { RefObject } from 'react';
|
||||||
|
|
||||||
|
import { CoordinateExtent, Node, NodeDragItem, NodeInternals, XYPosition } from '../../types';
|
||||||
|
import { clampPosition } from '../../utils';
|
||||||
|
|
||||||
|
export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean {
|
||||||
|
if (!node.parentNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentNode = nodeInternals.get(node.parentNode);
|
||||||
|
|
||||||
|
if (!parentNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentNode.selected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isParentSelected(parentNode, nodeInternals);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getParentNodePosition(nodeInternals: NodeInternals, nodeId?: string): XYPosition {
|
||||||
|
const parentNodeId = nodeId ? nodeInternals.get(nodeId)?.parentNode : null;
|
||||||
|
const parentNode = parentNodeId ? nodeInternals.get(parentNodeId) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: parentNode?.positionAbsolute?.x || 0,
|
||||||
|
y: parentNode?.positionAbsolute?.y || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectorExistsTargetToNode(target: Element, selector: string, nodeRef: RefObject<Element>): boolean {
|
||||||
|
let current = target;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (current?.matches(selector)) return true;
|
||||||
|
if (current === nodeRef.current) return false;
|
||||||
|
current = current.parentElement as Element;
|
||||||
|
} while (current);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// looks for all selected nodes and created a NodeDragItem for each of them
|
||||||
|
export function getDragItems(nodeInternals: NodeInternals, mousePos: XYPosition): NodeDragItem[] {
|
||||||
|
return Array.from(nodeInternals.values())
|
||||||
|
.filter((n) => n.selected && (!n.parentNode || !isParentSelected(n, nodeInternals)))
|
||||||
|
.map((n) => ({
|
||||||
|
id: n.id,
|
||||||
|
position: n.position,
|
||||||
|
distance: {
|
||||||
|
x: mousePos.x - n.position.x,
|
||||||
|
y: mousePos.y - n.position.y,
|
||||||
|
},
|
||||||
|
delta: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
extent: n.extent,
|
||||||
|
parentNode: n.parentNode,
|
||||||
|
width: n.width,
|
||||||
|
height: n.height,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePosition(
|
||||||
|
dragItem: NodeDragItem,
|
||||||
|
mousePos: XYPosition,
|
||||||
|
nodeInternals: NodeInternals,
|
||||||
|
nodeExtent?: CoordinateExtent
|
||||||
|
): NodeDragItem {
|
||||||
|
let currentExtent = dragItem.extent || nodeExtent;
|
||||||
|
let nextPosition = { x: mousePos.x - dragItem.distance.x, y: mousePos.y - dragItem.distance.y };
|
||||||
|
|
||||||
|
if (dragItem.extent === 'parent') {
|
||||||
|
if (dragItem.parentNode && dragItem.width && dragItem.height) {
|
||||||
|
const parent = nodeInternals.get(dragItem.parentNode);
|
||||||
|
currentExtent =
|
||||||
|
parent?.width && parent?.height
|
||||||
|
? [
|
||||||
|
[0, 0],
|
||||||
|
[parent.width - dragItem.width, parent.height - dragItem.height],
|
||||||
|
]
|
||||||
|
: currentExtent;
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[React Flow]: Only child nodes can use a parent extent. Help: https://reactflow.dev/error#500');
|
||||||
|
}
|
||||||
|
currentExtent = nodeExtent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPosition = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition;
|
||||||
|
|
||||||
|
dragItem.delta = {
|
||||||
|
x: nextPosition.x - dragItem.position.x,
|
||||||
|
y: nextPosition.y - dragItem.position.y,
|
||||||
|
};
|
||||||
|
dragItem.position = nextPosition;
|
||||||
|
|
||||||
|
return dragItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns two params:
|
||||||
|
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
||||||
|
// 2. array of selected nodes (handy when multi selection is active)
|
||||||
|
export function getEventHandlerParams({
|
||||||
|
nodeId,
|
||||||
|
dragItems,
|
||||||
|
nodeInternals,
|
||||||
|
}: {
|
||||||
|
nodeId?: string;
|
||||||
|
dragItems: NodeDragItem[];
|
||||||
|
nodeInternals: NodeInternals;
|
||||||
|
}): [Node, Node[]] {
|
||||||
|
const extentedDragItems: Node[] = dragItems.map((n) => {
|
||||||
|
const node = nodeInternals.get(n.id)!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
position: n.position,
|
||||||
|
positionAbsolute: {
|
||||||
|
x: (node.positionAbsolute?.x || 0) + n.delta.x,
|
||||||
|
y: (node.positionAbsolute?.y || 0) + n.delta.y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [nodeId ? extentedDragItems.find((n) => n.id === nodeId)! : extentedDragItems[0], extentedDragItems];
|
||||||
|
}
|
||||||
+10
-13
@@ -9,21 +9,19 @@ import {
|
|||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
NodeDimensionUpdate,
|
NodeDimensionUpdate,
|
||||||
NodeDiffUpdate,
|
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
NodeDimensionChange,
|
NodeDimensionChange,
|
||||||
EdgeSelectionChange,
|
EdgeSelectionChange,
|
||||||
NodeSelectionChange,
|
NodeSelectionChange,
|
||||||
NodePositionChange,
|
NodePositionChange,
|
||||||
|
NodeDragItem,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
import {
|
import {
|
||||||
createNodeInternals,
|
createNodeInternals,
|
||||||
createPositionChange,
|
|
||||||
handleControlledEdgeSelectionChange,
|
handleControlledEdgeSelectionChange,
|
||||||
handleControlledNodeSelectionChange,
|
handleControlledNodeSelectionChange,
|
||||||
isParentSelected,
|
|
||||||
fitView,
|
fitView,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import initialState from './initialState';
|
import initialState from './initialState';
|
||||||
@@ -96,20 +94,19 @@ const createStore = () =>
|
|||||||
onNodesChange?.(changes);
|
onNodesChange?.(changes);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateNodePosition: ({ id, diff }: NodeDiffUpdate) => {
|
updateNodePositions: (nodeDragItems: NodeDragItem[]) => {
|
||||||
const { onNodesChange, nodeExtent, nodeInternals, hasDefaultNodes, snapGrid, snapToGrid } = get();
|
const { onNodesChange, nodeInternals, hasDefaultNodes } = get();
|
||||||
|
|
||||||
if (hasDefaultNodes || onNodesChange) {
|
if (hasDefaultNodes || onNodesChange) {
|
||||||
const changes: NodePositionChange[] = [];
|
const changes: NodePositionChange[] = [];
|
||||||
|
|
||||||
nodeInternals.forEach((node) => {
|
nodeDragItems.forEach((node) => {
|
||||||
if (node.selected) {
|
const change: NodePositionChange = {
|
||||||
if (!node.parentNode || !isParentSelected(node, nodeInternals)) {
|
id: node.id,
|
||||||
changes.push(createPositionChange({ node, diff, nodeExtent, nodeInternals, snapToGrid, snapGrid }));
|
type: 'position',
|
||||||
}
|
position: node.position,
|
||||||
} else if (node.id === id) {
|
};
|
||||||
changes.push(createPositionChange({ node, diff, nodeExtent, nodeInternals, snapToGrid, snapGrid }));
|
changes.push(change);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (changes?.length) {
|
if (changes?.length) {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const initialState: ReactFlowStore = {
|
|||||||
onEdgesChange: null,
|
onEdgesChange: null,
|
||||||
hasDefaultNodes: false,
|
hasDefaultNodes: false,
|
||||||
hasDefaultEdges: false,
|
hasDefaultEdges: false,
|
||||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
|
||||||
d3Zoom: null,
|
d3Zoom: null,
|
||||||
d3Selection: null,
|
d3Selection: null,
|
||||||
d3ZoomHandler: undefined,
|
d3ZoomHandler: undefined,
|
||||||
|
|||||||
@@ -92,24 +92,6 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
|
|||||||
return nextNodeInternals;
|
return nextNodeInternals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean {
|
|
||||||
if (!node.parentNode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentNode = nodeInternals.get(node.parentNode);
|
|
||||||
|
|
||||||
if (!parentNode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentNode.selected) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isParentSelected(parentNode, nodeInternals);
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreatePostionChangeParams = {
|
type CreatePostionChangeParams = {
|
||||||
node: Node;
|
node: Node;
|
||||||
nodeExtent: CoordinateExtent;
|
nodeExtent: CoordinateExtent;
|
||||||
@@ -119,49 +101,6 @@ type CreatePostionChangeParams = {
|
|||||||
snapGrid?: SnapGrid;
|
snapGrid?: SnapGrid;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createPositionChange({
|
|
||||||
node,
|
|
||||||
diff,
|
|
||||||
nodeExtent,
|
|
||||||
nodeInternals,
|
|
||||||
snapToGrid,
|
|
||||||
snapGrid,
|
|
||||||
}: CreatePostionChangeParams): NodePositionChange {
|
|
||||||
const change: NodePositionChange = {
|
|
||||||
id: node.id,
|
|
||||||
type: 'position',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (diff) {
|
|
||||||
const nextPosition = { x: diff.x, y: diff.y };
|
|
||||||
|
|
||||||
let currentExtent = node.extent || nodeExtent;
|
|
||||||
|
|
||||||
if (node.extent === 'parent') {
|
|
||||||
if (node.parentNode && node.width && node.height) {
|
|
||||||
const parent = nodeInternals.get(node.parentNode);
|
|
||||||
currentExtent =
|
|
||||||
parent?.width && parent?.height
|
|
||||||
? [
|
|
||||||
[0, 0],
|
|
||||||
[parent.width - node.width, parent.height - node.height],
|
|
||||||
]
|
|
||||||
: currentExtent;
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
console.warn('[React Flow]: Only child nodes can use a parent extent. Help: https://reactflow.dev/error#500');
|
|
||||||
}
|
|
||||||
currentExtent = nodeExtent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
change.position = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return change;
|
|
||||||
}
|
|
||||||
|
|
||||||
type InternalFitViewOptions = {
|
type InternalFitViewOptions = {
|
||||||
initial?: boolean;
|
initial?: boolean;
|
||||||
} & FitViewOptions;
|
} & FitViewOptions;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import {
|
|||||||
OnMove,
|
OnMove,
|
||||||
OnMoveStart,
|
OnMoveStart,
|
||||||
OnMoveEnd,
|
OnMoveEnd,
|
||||||
|
NodeDragHandler,
|
||||||
|
NodeMouseHandler,
|
||||||
} from '.';
|
} from '.';
|
||||||
import { HandleType } from './handles';
|
import { HandleType } from './handles';
|
||||||
|
|
||||||
@@ -47,16 +49,16 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
|
|||||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||||
onNodesChange?: OnNodesChange;
|
onNodesChange?: OnNodesChange;
|
||||||
onEdgesChange?: OnEdgesChange;
|
onEdgesChange?: OnEdgesChange;
|
||||||
onNodeClick?: (event: React.MouseEvent, node: Node) => void;
|
onNodeClick?: NodeMouseHandler;
|
||||||
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
|
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
|
||||||
onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDoubleClick?: NodeMouseHandler;
|
||||||
onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeMouseEnter?: NodeMouseHandler;
|
||||||
onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeMouseMove?: NodeMouseHandler;
|
||||||
onNodeMouseLeave?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeMouseLeave?: NodeMouseHandler;
|
||||||
onNodeContextMenu?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeContextMenu?: NodeMouseHandler;
|
||||||
onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDragStart?: NodeDragHandler;
|
||||||
onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDrag?: NodeDragHandler;
|
||||||
onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDragStop?: NodeDragHandler;
|
||||||
onNodesDelete?: OnNodesDelete;
|
onNodesDelete?: OnNodesDelete;
|
||||||
onEdgesDelete?: OnEdgesDelete;
|
onEdgesDelete?: OnEdgesDelete;
|
||||||
onConnect?: OnConnect;
|
onConnect?: OnConnect;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Selection as D3Selection, ZoomBehavior } from 'd3';
|
|||||||
|
|
||||||
import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
|
import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
|
||||||
import { NodeChange, EdgeChange } from './changes';
|
import { NodeChange, EdgeChange } from './changes';
|
||||||
import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate, NodeProps, WrapNodeProps } from './nodes';
|
import { Node, NodeInternals, NodeDimensionUpdate, NodeProps, WrapNodeProps, NodeDragItem } from './nodes';
|
||||||
import { Edge, EdgeProps, WrapEdgeProps } from './edges';
|
import { Edge, EdgeProps, WrapEdgeProps } from './edges';
|
||||||
import { HandleType, StartHandle } from './handles';
|
import { HandleType, StartHandle } from './handles';
|
||||||
import { DefaultEdgeOptions } from '.';
|
import { DefaultEdgeOptions } from '.';
|
||||||
@@ -125,7 +125,6 @@ export type ReactFlowStore = {
|
|||||||
transform: Transform;
|
transform: Transform;
|
||||||
nodeInternals: NodeInternals;
|
nodeInternals: NodeInternals;
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
selectedNodesBbox: Rect;
|
|
||||||
onNodesChange: OnNodesChange | null;
|
onNodesChange: OnNodesChange | null;
|
||||||
onEdgesChange: OnEdgesChange | null;
|
onEdgesChange: OnEdgesChange | null;
|
||||||
hasDefaultNodes: boolean;
|
hasDefaultNodes: boolean;
|
||||||
@@ -182,7 +181,7 @@ export type ReactFlowActions = {
|
|||||||
setEdges: (edges: Edge[]) => void;
|
setEdges: (edges: Edge[]) => void;
|
||||||
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
|
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
|
||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||||
updateNodePosition: (update: NodeDiffUpdate) => void;
|
updateNodePositions: (nodeDragItems: NodeDragItem[]) => void;
|
||||||
resetSelectedElements: () => void;
|
resetSelectedElements: () => void;
|
||||||
unselectNodesAndEdges: () => void;
|
unselectNodesAndEdges: () => void;
|
||||||
addSelectedNodes: (nodeIds: string[]) => void;
|
addSelectedNodes: (nodeIds: string[]) => void;
|
||||||
|
|||||||
+18
-8
@@ -50,6 +50,7 @@ export interface NodeProps<T = any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void;
|
export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void;
|
||||||
|
export type NodeDragHandler = (event: ReactMouseEvent, node: Node, nodes: Node[]) => void;
|
||||||
|
|
||||||
export interface WrapNodeProps<T = any> {
|
export interface WrapNodeProps<T = any> {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -70,9 +71,9 @@ export interface WrapNodeProps<T = any> {
|
|||||||
onMouseMove?: NodeMouseHandler;
|
onMouseMove?: NodeMouseHandler;
|
||||||
onMouseLeave?: NodeMouseHandler;
|
onMouseLeave?: NodeMouseHandler;
|
||||||
onContextMenu?: NodeMouseHandler;
|
onContextMenu?: NodeMouseHandler;
|
||||||
onNodeDragStart?: NodeMouseHandler;
|
onNodeDragStart?: NodeDragHandler;
|
||||||
onNodeDrag?: NodeMouseHandler;
|
onNodeDrag?: NodeDragHandler;
|
||||||
onNodeDragStop?: NodeMouseHandler;
|
onNodeDragStop?: NodeDragHandler;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
sourcePosition: Position;
|
sourcePosition: Position;
|
||||||
@@ -91,11 +92,6 @@ export type NodeHandleBounds = {
|
|||||||
target: HandleElement[] | null;
|
target: HandleElement[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeDiffUpdate = {
|
|
||||||
id?: string;
|
|
||||||
diff?: XYPosition;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NodeDimensionUpdate = {
|
export type NodeDimensionUpdate = {
|
||||||
id: string;
|
id: string;
|
||||||
nodeElement: HTMLDivElement;
|
nodeElement: HTMLDivElement;
|
||||||
@@ -108,3 +104,17 @@ export type NodeBounds = XYPosition & {
|
|||||||
width: number | null;
|
width: number | null;
|
||||||
height: number | null;
|
height: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NodeDragItem = {
|
||||||
|
id: string;
|
||||||
|
// relative node position
|
||||||
|
position: XYPosition;
|
||||||
|
// distance from the mouse cursor to the node when start dragging
|
||||||
|
distance: XYPosition;
|
||||||
|
// delta to previous position
|
||||||
|
delta: XYPosition;
|
||||||
|
width?: number | null;
|
||||||
|
height?: number | null;
|
||||||
|
extent?: 'parent' | CoordinateExtent;
|
||||||
|
parentNode?: string;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user