Merge pull request #1732 from wbkd/refactor/selection
Refactor/selection
This commit is contained in:
@@ -3,17 +3,15 @@ import { useState, MouseEvent, useCallback } from 'react';
|
|||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
Background,
|
Background,
|
||||||
applyNodeChanges,
|
|
||||||
applyEdgeChanges,
|
|
||||||
MiniMap,
|
MiniMap,
|
||||||
Controls,
|
Controls,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
NodeChange,
|
|
||||||
EdgeChange,
|
|
||||||
OnLoadParams,
|
OnLoadParams,
|
||||||
Connection,
|
Connection,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
import DebugNode from './DebugNode';
|
import DebugNode from './DebugNode';
|
||||||
|
|
||||||
@@ -108,12 +106,12 @@ const nodeTypes = {
|
|||||||
|
|
||||||
const BasicFlow = () => {
|
const BasicFlow = () => {
|
||||||
const [rfInstance, setRfInstance] = useState<OnLoadParams | null>(null);
|
const [rfInstance, setRfInstance] = useState<OnLoadParams | null>(null);
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const onConnect = useCallback((params: Edge | Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => {
|
setEdges((eds) => {
|
||||||
return addEdge(params, eds);
|
return addEdge(connection, eds);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
||||||
@@ -152,14 +150,6 @@ const BasicFlow = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
|
||||||
setEdges((es) => applyEdgeChanges(changes, es));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, MouseEvent, useCallback } from 'react';
|
import { useState, useEffect, MouseEvent } from 'react';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
@@ -10,11 +10,8 @@ import ReactFlow, {
|
|||||||
Position,
|
Position,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
useNodesState,
|
||||||
NodeChange,
|
useEdgesState,
|
||||||
applyNodeChanges,
|
|
||||||
applyEdgeChanges,
|
|
||||||
EdgeChange,
|
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
import ColorSelectorNode from './ColorSelectorNode';
|
import ColorSelectorNode from './ColorSelectorNode';
|
||||||
@@ -35,8 +32,9 @@ const nodeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CustomNodeFlow = () => {
|
const CustomNodeFlow = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges] = useState<Edge[]>([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
|
|
||||||
const [bgColor, setBgColor] = useState<string>(initBgColor);
|
const [bgColor, setBgColor] = useState<string>(initBgColor);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -100,16 +98,8 @@ const CustomNodeFlow = () => {
|
|||||||
]);
|
]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onConnect = (params: Connection | Edge) =>
|
const onConnect = (connection: Connection) =>
|
||||||
setEdges((eds) => addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, eds));
|
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds));
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
|
||||||
setEdges((es) => applyEdgeChanges(changes, es));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
Background,
|
Background,
|
||||||
OnLoadParams,
|
OnLoadParams,
|
||||||
EdgeTypesType,
|
EdgeTypesType,
|
||||||
Node,
|
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
useNodesState,
|
||||||
applyNodeChanges,
|
useEdgesState,
|
||||||
applyEdgeChanges,
|
|
||||||
NodeChange,
|
|
||||||
EdgeChange,
|
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
import './style.css';
|
import './style.css';
|
||||||
@@ -29,19 +25,11 @@ const edgeTypes: EdgeTypesType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FloatingEdges = () => {
|
const FloatingEdges = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const onConnect = useCallback((params: Edge | Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => addEdge(params, eds));
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
|
||||||
setEdges((es) => applyEdgeChanges(changes, es));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import ReactFlow, { addEdge, MiniMap, Controls, Connection, Edge, Node } from 'react-flow-renderer';
|
import ReactFlow, {
|
||||||
|
addEdge,
|
||||||
|
MiniMap,
|
||||||
|
Controls,
|
||||||
|
Connection,
|
||||||
|
Edge,
|
||||||
|
Node,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
const initialNodes: Node[] = [
|
||||||
{ id: '1', type: 'input', hidden: true, data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
{ id: '1', type: 'input', hidden: true, data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||||
@@ -23,12 +32,13 @@ const setHidden = (hidden: boolean) => (els: any[]) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const HiddenFlow = () => {
|
const HiddenFlow = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||||
|
|
||||||
const onConnect = useCallback((params: Edge | Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => addEdge(params, eds));
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
addEdge,
|
addEdge,
|
||||||
@@ -7,15 +7,15 @@ import ReactFlow, {
|
|||||||
Controls,
|
Controls,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
Node,
|
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
Position,
|
Position,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
import dagre from 'dagre';
|
import dagre from 'dagre';
|
||||||
|
|
||||||
import initialNodesAndEdges from './initial-elements';
|
import initialItems from './initial-elements';
|
||||||
|
|
||||||
import './layouting.css';
|
import './layouting.css';
|
||||||
|
|
||||||
@@ -28,13 +28,11 @@ const nodeExtent: CoordinateExtent = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const LayoutFlow = () => {
|
const LayoutFlow = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodesAndEdges.nodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialItems.nodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialNodesAndEdges.edges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialItems.edges);
|
||||||
|
|
||||||
const onConnect = useCallback((params: Edge | Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => {
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
return addEdge(params, eds);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onLayout = (direction: string) => {
|
const onLayout = (direction: string) => {
|
||||||
@@ -65,12 +63,6 @@ const LayoutFlow = () => {
|
|||||||
setNodes(layoutedNodes);
|
setNodes(layoutedNodes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => setNodes((ns) => applyNodeChanges(changes, ns)), []);
|
|
||||||
|
|
||||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
|
||||||
setEdges((es) => applyEdgeChanges(changes, es));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="layoutflow">
|
<div className="layoutflow">
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ import { useState, MouseEvent, useCallback } from 'react';
|
|||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
Background,
|
Background,
|
||||||
applyNodeChanges,
|
useNodesState,
|
||||||
applyEdgeChanges,
|
useEdgesState,
|
||||||
MiniMap,
|
MiniMap,
|
||||||
Controls,
|
Controls,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
NodeChange,
|
|
||||||
EdgeChange,
|
|
||||||
OnLoadParams,
|
OnLoadParams,
|
||||||
Connection,
|
Connection,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
@@ -85,19 +83,13 @@ const initialEdges: Edge[] = [
|
|||||||
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function shuffle() {
|
|
||||||
return Math.random() - Math.random();
|
|
||||||
}
|
|
||||||
|
|
||||||
const NestedFlow = () => {
|
const NestedFlow = () => {
|
||||||
const [rfInstance, setRfInstance] = useState<OnLoadParams | null>(null);
|
const [rfInstance, setRfInstance] = useState<OnLoadParams | null>(null);
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const onConnect = useCallback((params: Edge | Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => {
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
return addEdge(params, eds);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
||||||
|
|
||||||
@@ -135,14 +127,6 @@ const NestedFlow = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
|
||||||
setEdges((es) => applyEdgeChanges(changes, es));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(30, 30);
|
|||||||
const StressFlow = () => {
|
const StressFlow = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||||
const onConnect = useCallback((params: Edge | Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => addEdge(params, eds));
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
}, []);
|
}, []);
|
||||||
const updatePos = () => {
|
const updatePos = () => {
|
||||||
setNodes((nds) => {
|
setNodes((nds) => {
|
||||||
@@ -51,8 +51,6 @@ const StressFlow = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||||
console.log('node change', changes);
|
|
||||||
|
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
setNodes((ns) => applyNodeChanges(changes, ns));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,10 @@ const UpdatableEdge = () => {
|
|||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||||
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
|
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
|
||||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||||
const onConnect = (params: Connection | Edge) => setEdges((els) => addEdge(params, els));
|
const onConnect = (connection: Connection) => setEdges((els) => addEdge(connection, els));
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||||
|
console.log(changes);
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
setNodes((ns) => applyNodeChanges(changes, ns));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import ReactFlow, { Node, Edge, applyNodeChanges, NodeChange } from 'react-flow-renderer';
|
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
||||||
|
|
||||||
import './updatenode.css';
|
import './updatenode.css';
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@ const initialNodes: Node[] = [
|
|||||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||||
|
|
||||||
const UpdateNode = () => {
|
const UpdateNode = () => {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const [nodeName, setNodeName] = useState<string>('Node 1');
|
const [nodeName, setNodeName] = useState<string>('Node 1');
|
||||||
const [nodeBg, setNodeBg] = useState<string>('#eee');
|
const [nodeBg, setNodeBg] = useState<string>('#eee');
|
||||||
@@ -60,12 +60,16 @@ const UpdateNode = () => {
|
|||||||
);
|
);
|
||||||
}, [nodeHidden]);
|
}, [nodeHidden]);
|
||||||
|
|
||||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
||||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow nodes={nodes} edges={edges} defaultZoom={1.5} minZoom={0.2} maxZoom={4} onNodesChange={onNodesChange}>
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
defaultZoom={1.5}
|
||||||
|
minZoom={0.2}
|
||||||
|
maxZoom={4}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
>
|
||||||
<div className="updatenode__controls">
|
<div className="updatenode__controls">
|
||||||
<label>label:</label>
|
<label>label:</label>
|
||||||
<input value={nodeName} onChange={(evt) => setNodeName(evt.target.value)} />
|
<input value={nodeName} onChange={(evt) => setNodeName(evt.target.value)} />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import cc from 'classcat';
|
|||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { getRectOfNodes } from '../../utils/graph';
|
import { getRectOfNodeInternals } from '../../utils/graph';
|
||||||
import { getBoundsofRects } from '../../utils';
|
import { getBoundsofRects } from '../../utils';
|
||||||
import { Node, ReactFlowState, Rect } from '../../types';
|
import { Node, ReactFlowState, Rect } from '../../types';
|
||||||
import MiniMapNode from './MiniMapNode';
|
import MiniMapNode from './MiniMapNode';
|
||||||
@@ -55,7 +55,7 @@ const MiniMap = ({
|
|||||||
const hasNodes = nodeInternals && nodeInternals.size > 0;
|
const hasNodes = nodeInternals && nodeInternals.size > 0;
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
// @TODO: work with nodeInternals instead of converting it to an array
|
||||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||||
const bb = getRectOfNodes(nodes);
|
const bb = getRectOfNodeInternals(nodes);
|
||||||
const viewBB: Rect = {
|
const viewBB: Rect = {
|
||||||
x: -tX / tScale,
|
x: -tX / tScale,
|
||||||
y: -tY / tScale,
|
y: -tY / tScale,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// These components are not used by React Flow directly
|
// These components are not used by React Flow directly
|
||||||
// but the user can add them as children of a React Flow component
|
// They can be added as children of a React Flow component
|
||||||
|
|
||||||
export { default as MiniMap } from './MiniMap';
|
export { default as MiniMap } from './MiniMap';
|
||||||
export { default as Controls, ControlButton } from './Controls';
|
export { default as Controls, ControlButton } from './Controls';
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { EdgeAnchor } from './EdgeAnchor';
|
|||||||
import { getMarkerId } from '../../utils/graph';
|
import { getMarkerId } from '../../utils/graph';
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
addSelectedElements: s.addSelectedElements,
|
addSelectedEdges: s.addSelectedEdges,
|
||||||
setConnectionNodeId: s.setConnectionNodeId,
|
setConnectionNodeId: s.setConnectionNodeId,
|
||||||
unsetNodesSelection: s.unsetNodesSelection,
|
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||||
setPosition: s.setConnectionPosition,
|
setPosition: s.setConnectionPosition,
|
||||||
connectionMode: s.connectionMode,
|
connectionMode: s.connectionMode,
|
||||||
});
|
});
|
||||||
@@ -57,7 +57,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
markerStart,
|
markerStart,
|
||||||
}: WrapEdgeProps): JSX.Element | null => {
|
}: WrapEdgeProps): JSX.Element | null => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { addSelectedElements, setConnectionNodeId, unsetNodesSelection, setPosition, connectionMode } = useStore(
|
const { addSelectedEdges, setConnectionNodeId, setNodesSelectionActive, setPosition, connectionMode } = useStore(
|
||||||
selector,
|
selector,
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
@@ -99,8 +99,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
const onEdgeClick = useCallback(
|
const onEdgeClick = useCallback(
|
||||||
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||||
if (elementsSelectable) {
|
if (elementsSelectable) {
|
||||||
unsetNodesSelection();
|
setNodesSelectionActive(false);
|
||||||
addSelectedElements([edgeElement]);
|
addSelectedEdges([edgeElement.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick?.(event, edgeElement);
|
onClick?.(event, edgeElement);
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import { Provider } from '../../contexts/NodeIdContext';
|
|||||||
import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
|
import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
addSelectedElements: s.addSelectedElements,
|
addSelectedNodes: s.addSelectedNodes,
|
||||||
unsetNodesSelection: s.unsetNodesSelection,
|
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||||
updateNodePosition: s.updateNodePosition,
|
updateNodePosition: s.updateNodePosition,
|
||||||
updateNodeDimensions: s.updateNodeDimensions,
|
|
||||||
unselectNodesAndEdges: s.unselectNodesAndEdges,
|
unselectNodesAndEdges: s.unselectNodesAndEdges,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,84 +52,67 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
noPanClassName,
|
noPanClassName,
|
||||||
noDragClassName,
|
noDragClassName,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
const {
|
const { addSelectedNodes, unselectNodesAndEdges, setNodesSelectionActive, updateNodePosition } = useStore(
|
||||||
addSelectedElements,
|
selector,
|
||||||
unselectNodesAndEdges,
|
shallow
|
||||||
unsetNodesSelection,
|
);
|
||||||
updateNodePosition,
|
|
||||||
// updateNodeDimensions,
|
|
||||||
} = useStore(selector, shallow);
|
|
||||||
const nodeElement = useRef<HTMLDivElement>(null);
|
const nodeElement = useRef<HTMLDivElement>(null);
|
||||||
|
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||||
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
|
|
||||||
const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]);
|
|
||||||
|
|
||||||
const nodeStyle: CSSProperties = useMemo(
|
const nodeStyle: CSSProperties = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
zIndex,
|
zIndex,
|
||||||
transform: `translate(${xPos}px,${yPos}px)`,
|
transform: `translate(${xPos}px,${yPos}px)`,
|
||||||
pointerEvents:
|
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
||||||
isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none',
|
|
||||||
// prevents jumping of nodes on start
|
// prevents jumping of nodes on start
|
||||||
opacity: isInitialized ? 1 : 0,
|
opacity: isInitialized ? 1 : 0,
|
||||||
...style,
|
...style,
|
||||||
}),
|
}),
|
||||||
[
|
[zIndex, xPos, yPos, hasPointerEvents, isInitialized, style]
|
||||||
xPos,
|
|
||||||
yPos,
|
|
||||||
isSelectable,
|
|
||||||
isDraggable,
|
|
||||||
onClick,
|
|
||||||
isInitialized,
|
|
||||||
style,
|
|
||||||
onMouseEnter,
|
|
||||||
onMouseMove,
|
|
||||||
onMouseLeave,
|
|
||||||
isParent,
|
|
||||||
zIndex,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseEnterHandler = useMemo(() => {
|
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
|
||||||
if (!onMouseEnter || dragging) {
|
const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (event: MouseEvent) => onMouseEnter(event, node);
|
const onMouseEnterHandler = useCallback(
|
||||||
}, [onMouseEnter, dragging, node]);
|
(event: MouseEvent) => {
|
||||||
|
if (onMouseEnter && !dragging) {
|
||||||
|
onMouseEnter(event, node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onMouseEnter, dragging, node]
|
||||||
|
);
|
||||||
|
|
||||||
const onMouseMoveHandler = useMemo(() => {
|
const onMouseMoveHandler = useCallback(
|
||||||
if (!onMouseMove || dragging) {
|
(event: MouseEvent) => {
|
||||||
return;
|
if (onMouseMove && !dragging) {
|
||||||
}
|
onMouseMove(event, node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onMouseMove, dragging, node]
|
||||||
|
);
|
||||||
|
|
||||||
return (event: MouseEvent) => onMouseMove(event, node);
|
const onMouseLeaveHandler = useCallback(
|
||||||
}, [onMouseMove, dragging, node]);
|
(event: MouseEvent) => {
|
||||||
|
if (onMouseLeave && !dragging) {
|
||||||
|
onMouseLeave?.(event, node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onMouseLeave, dragging, node]
|
||||||
|
);
|
||||||
|
|
||||||
const onMouseLeaveHandler = useMemo(() => {
|
const onContextMenuHandler = useCallback(
|
||||||
if (!onMouseLeave || dragging) {
|
(event: MouseEvent) => onContextMenu?.(event, node),
|
||||||
return;
|
[onContextMenu, node]
|
||||||
}
|
);
|
||||||
|
|
||||||
return (event: MouseEvent) => onMouseLeave(event, node);
|
|
||||||
}, [onMouseLeave, dragging, node]);
|
|
||||||
|
|
||||||
const onContextMenuHandler = useMemo(() => {
|
|
||||||
if (!onContextMenu) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (event: MouseEvent) => onContextMenu(event, node);
|
|
||||||
}, [onContextMenu, node]);
|
|
||||||
|
|
||||||
const onSelectNodeHandler = useCallback(
|
const onSelectNodeHandler = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
if (!isDraggable) {
|
if (!isDraggable) {
|
||||||
if (isSelectable) {
|
if (isSelectable) {
|
||||||
unsetNodesSelection();
|
setNodesSelectionActive(false);
|
||||||
|
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
addSelectedElements([node]);
|
addSelectedNodes([node.id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,14 +127,14 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
onNodeDragStart?.(event as MouseEvent, node);
|
onNodeDragStart?.(event as MouseEvent, node);
|
||||||
|
|
||||||
if (selectNodesOnDrag && isSelectable) {
|
if (selectNodesOnDrag && isSelectable) {
|
||||||
unsetNodesSelection();
|
setNodesSelectionActive(false);
|
||||||
|
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
addSelectedElements([node]);
|
addSelectedNodes([node.id]);
|
||||||
}
|
}
|
||||||
} else if (!selectNodesOnDrag && !selected && isSelectable) {
|
} else if (!selectNodesOnDrag && !selected && isSelectable) {
|
||||||
unselectNodesAndEdges();
|
unselectNodesAndEdges();
|
||||||
unsetNodesSelection();
|
setNodesSelectionActive(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
[node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
||||||
@@ -163,9 +145,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
node.position.x += draggableData.deltaX;
|
node.position.x += draggableData.deltaX;
|
||||||
node.position.y += draggableData.deltaY;
|
node.position.y += draggableData.deltaY;
|
||||||
|
|
||||||
if (onNodeDrag) {
|
onNodeDrag?.(event as MouseEvent, node);
|
||||||
onNodeDrag(event as MouseEvent, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodePosition({ id, dragging: true, diff: { x: draggableData.deltaX, y: draggableData.deltaY } });
|
updateNodePosition({ id, dragging: true, diff: { x: draggableData.deltaX, y: draggableData.deltaY } });
|
||||||
},
|
},
|
||||||
@@ -178,7 +158,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
|
|||||||
// Because of that we set dragging to true inside the onDrag handler and handle the click here
|
// Because of that we set dragging to true inside the onDrag handler and handle the click here
|
||||||
if (!dragging) {
|
if (!dragging) {
|
||||||
if (isSelectable && !selectNodesOnDrag && !selected) {
|
if (isSelectable && !selectNodesOnDrag && !selected) {
|
||||||
addSelectedElements([node]);
|
addSelectedNodes([node.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick?.(event as MouseEvent, node);
|
onClick?.(event as MouseEvent, node);
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
* made a selectio with on or several nodes
|
* made a selectio with on or several nodes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useCallback, useRef, MouseEvent } from 'react';
|
import React, { memo, useMemo, useCallback, useRef, MouseEvent } from 'react';
|
||||||
import ReactDraggable, { DraggableData } from 'react-draggable';
|
import { DraggableCore, DraggableData } from 'react-draggable';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { Node, ReactFlowState } from '../../types';
|
import { Node, ReactFlowState } from '../../types';
|
||||||
|
import { getRectOfNodeInternals } from '../../utils/graph';
|
||||||
|
|
||||||
export interface NodesSelectionProps {
|
export interface NodesSelectionProps {
|
||||||
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
|
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
|
||||||
@@ -21,7 +22,7 @@ export interface NodesSelectionProps {
|
|||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
transform: s.transform,
|
transform: s.transform,
|
||||||
selectedNodesBbox: s.selectedNodesBbox,
|
selectedNodesBbox: s.selectedNodesBbox,
|
||||||
selectionActive: s.selectionActive,
|
userSelectionActive: s.userSelectionActive,
|
||||||
selectedNodes: Array.from(s.nodeInternals)
|
selectedNodes: Array.from(s.nodeInternals)
|
||||||
.filter(([_, n]) => n.selected)
|
.filter(([_, n]) => n.selected)
|
||||||
.map(([_, n]) => n),
|
.map(([_, n]) => n),
|
||||||
@@ -30,14 +31,14 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
updateNodePosition: s.updateNodePosition,
|
updateNodePosition: s.updateNodePosition,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ({
|
function NodesSelection({
|
||||||
onSelectionDragStart,
|
onSelectionDragStart,
|
||||||
onSelectionDrag,
|
onSelectionDrag,
|
||||||
onSelectionDragStop,
|
onSelectionDragStop,
|
||||||
onSelectionContextMenu,
|
onSelectionContextMenu,
|
||||||
noPanClassName,
|
noPanClassName,
|
||||||
}: NodesSelectionProps) => {
|
}: NodesSelectionProps) {
|
||||||
const { transform, selectedNodesBbox, selectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosition } =
|
const { transform, userSelectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosition } =
|
||||||
useStore(selector);
|
useStore(selector);
|
||||||
const [tX, tY, tScale] = transform;
|
const [tX, tY, tScale] = transform;
|
||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
@@ -51,6 +52,8 @@ export default ({
|
|||||||
[tX, tY, tScale]
|
[tX, tY, tScale]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedNodesBbox = useMemo(() => getRectOfNodeInternals(selectedNodes), [selectedNodes]);
|
||||||
|
|
||||||
const innerStyle = useMemo(
|
const innerStyle = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
width: selectedNodesBbox.width,
|
width: selectedNodesBbox.width,
|
||||||
@@ -70,10 +73,6 @@ export default ({
|
|||||||
|
|
||||||
const onDrag = useCallback(
|
const onDrag = useCallback(
|
||||||
(event: MouseEvent, data: DraggableData) => {
|
(event: MouseEvent, data: DraggableData) => {
|
||||||
if (onSelectionDrag) {
|
|
||||||
onSelectionDrag(event, selectedNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodePosition({
|
updateNodePosition({
|
||||||
diff: {
|
diff: {
|
||||||
x: data.deltaX,
|
x: data.deltaX,
|
||||||
@@ -81,6 +80,8 @@ export default ({
|
|||||||
},
|
},
|
||||||
dragging: true,
|
dragging: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onSelectionDrag?.(event, selectedNodes);
|
||||||
},
|
},
|
||||||
[onSelectionDrag, selectedNodes, updateNodePosition]
|
[onSelectionDrag, selectedNodes, updateNodePosition]
|
||||||
);
|
);
|
||||||
@@ -103,13 +104,13 @@ export default ({
|
|||||||
[onSelectionContextMenu, selectedNodes]
|
[onSelectionContextMenu, selectedNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!selectedNodes || selectionActive) {
|
if (!selectedNodes?.length || userSelectionActive) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName])} style={style}>
|
<div className={cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName])} style={style}>
|
||||||
<ReactDraggable
|
<DraggableCore
|
||||||
scale={tScale}
|
scale={tScale}
|
||||||
grid={grid}
|
grid={grid}
|
||||||
onStart={(event) => onStart(event as MouseEvent)}
|
onStart={(event) => onStart(event as MouseEvent)}
|
||||||
@@ -124,7 +125,9 @@ export default ({
|
|||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
style={innerStyle}
|
style={innerStyle}
|
||||||
/>
|
/>
|
||||||
</ReactDraggable>
|
</DraggableCore>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export default memo(NodesSelection);
|
||||||
|
|||||||
@@ -2,104 +2,145 @@
|
|||||||
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
|
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo, useState, useRef, useCallback } from 'react';
|
||||||
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore, useStoreApi } from '../../store';
|
||||||
import { XYPosition, ReactFlowState } from '../../types';
|
import { getSelectionChanges } from '../../utils/changes';
|
||||||
|
import { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types';
|
||||||
|
import { getConnectedEdges, getNodesInside } from '../../utils/graph';
|
||||||
|
|
||||||
|
type SelectionRect = Rect & {
|
||||||
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
draw: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type UserSelectionProps = {
|
type UserSelectionProps = {
|
||||||
selectionKeyPressed: boolean;
|
selectionKeyPressed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getMousePosition(event: React.MouseEvent): XYPosition | void {
|
function getMousePosition(event: React.MouseEvent, containerBounds: DOMRect): XYPosition {
|
||||||
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
|
||||||
if (!reactFlowNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerBounds = reactFlowNode.getBoundingClientRect();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: event.clientX - containerBounds.left,
|
x: event.clientX - containerBounds.left,
|
||||||
y: event.clientY - containerBounds.top,
|
y: event.clientY - containerBounds.top,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const userSelectionRectSelector = (state: ReactFlowState) => state.userSelectionRect;
|
|
||||||
|
|
||||||
const SelectionRect = () => {
|
|
||||||
const userSelectionRect = useStore(userSelectionRectSelector);
|
|
||||||
|
|
||||||
if (!userSelectionRect.draw) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="react-flow__selection react-flow__container"
|
|
||||||
style={{
|
|
||||||
width: userSelectionRect.width,
|
|
||||||
height: userSelectionRect.height,
|
|
||||||
transform: `translate(${userSelectionRect.x}px, ${userSelectionRect.y}px)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
selectionActive: s.selectionActive,
|
userSelectionActive: s.userSelectionActive,
|
||||||
elementsSelectable: s.elementsSelectable,
|
elementsSelectable: s.elementsSelectable,
|
||||||
setUserSelection: s.setUserSelection,
|
setUserSelectionActive: s.setUserSelectionActive,
|
||||||
updateUserSelection: s.updateUserSelection,
|
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||||
unsetUserSelection: s.unsetUserSelection,
|
|
||||||
unsetNodesSelection: s.unsetNodesSelection,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
|
const initialRect: SelectionRect = {
|
||||||
const {
|
startX: 0,
|
||||||
selectionActive,
|
startY: 0,
|
||||||
elementsSelectable,
|
x: 0,
|
||||||
setUserSelection,
|
y: 0,
|
||||||
updateUserSelection,
|
width: 0,
|
||||||
unsetUserSelection,
|
height: 0,
|
||||||
unsetNodesSelection,
|
draw: false,
|
||||||
} = useStore(selector);
|
};
|
||||||
|
|
||||||
const renderUserSelectionPane = selectionActive || selectionKeyPressed;
|
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
|
||||||
|
const store = useStoreApi();
|
||||||
|
const prevSelectedNodesCount = useRef<number>(0);
|
||||||
|
const prevSelectedEdgesCount = useRef<number>(0);
|
||||||
|
const containerBounds = useRef<DOMRect>();
|
||||||
|
const [userSelectionRect, setUserSelectionRect] = useState<SelectionRect>(initialRect);
|
||||||
|
const { userSelectionActive, elementsSelectable, setUserSelectionActive, setNodesSelectionActive } = useStore(
|
||||||
|
selector,
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderUserSelectionPane = userSelectionActive || selectionKeyPressed;
|
||||||
|
|
||||||
|
const resetUserSelection = useCallback(() => {
|
||||||
|
setUserSelectionRect(initialRect);
|
||||||
|
setUserSelectionActive(false);
|
||||||
|
|
||||||
|
prevSelectedNodesCount.current = 0;
|
||||||
|
prevSelectedEdgesCount.current = 0;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMouseDown = useCallback((event: React.MouseEvent): void => {
|
||||||
|
const reactFlowNode = (event.target as Element).closest('.react-flow')!;
|
||||||
|
containerBounds.current = reactFlowNode.getBoundingClientRect();
|
||||||
|
|
||||||
|
const mousePos = getMousePosition(event, containerBounds.current!);
|
||||||
|
|
||||||
|
setUserSelectionRect({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
startX: mousePos.x,
|
||||||
|
startY: mousePos.y,
|
||||||
|
x: mousePos.x,
|
||||||
|
y: mousePos.y,
|
||||||
|
draw: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUserSelectionActive(true);
|
||||||
|
setNodesSelectionActive(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMouseMove = (event: React.MouseEvent): void => {
|
||||||
|
if (!selectionKeyPressed || !userSelectionRect.draw || !containerBounds.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mousePos = getMousePosition(event, containerBounds.current!);
|
||||||
|
const startX = userSelectionRect.startX ?? 0;
|
||||||
|
const startY = userSelectionRect.startY ?? 0;
|
||||||
|
|
||||||
|
const nextUserSelectRect = {
|
||||||
|
...userSelectionRect,
|
||||||
|
x: mousePos.x < startX ? mousePos.x : userSelectionRect.x,
|
||||||
|
y: mousePos.y < startY ? mousePos.y : userSelectionRect.y,
|
||||||
|
width: Math.abs(mousePos.x - startX),
|
||||||
|
height: Math.abs(mousePos.y - startY),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { nodeInternals, edges, transform, onNodesChange, onEdgesChange } = store.getState();
|
||||||
|
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||||
|
const selectedNodes = getNodesInside(nodeInternals, nextUserSelectRect, transform, false, true);
|
||||||
|
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
||||||
|
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
||||||
|
|
||||||
|
if (prevSelectedNodesCount.current !== selectedNodeIds.length) {
|
||||||
|
prevSelectedNodesCount.current = selectedNodeIds.length;
|
||||||
|
const changes = getSelectionChanges(nodes, selectedNodeIds) as NodeChange[];
|
||||||
|
if (changes.length) {
|
||||||
|
onNodesChange?.(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevSelectedEdgesCount.current !== selectedEdgeIds.length) {
|
||||||
|
prevSelectedEdgesCount.current = selectedEdgeIds.length;
|
||||||
|
const changes = getSelectionChanges(edges, selectedEdgeIds) as EdgeChange[];
|
||||||
|
if (changes.length) {
|
||||||
|
onEdgesChange?.(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserSelectionRect(nextUserSelectRect);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = useCallback(() => {
|
||||||
|
setNodesSelectionActive(prevSelectedNodesCount.current > 0);
|
||||||
|
resetUserSelection();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMouseLeave = useCallback(() => {
|
||||||
|
setNodesSelectionActive(false);
|
||||||
|
resetUserSelection();
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!elementsSelectable || !renderUserSelectionPane) {
|
if (!elementsSelectable || !renderUserSelectionPane) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMouseDown = (event: React.MouseEvent): void => {
|
|
||||||
const mousePos = getMousePosition(event);
|
|
||||||
if (!mousePos) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUserSelection(mousePos);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseMove = (event: React.MouseEvent): void => {
|
|
||||||
if (!selectionKeyPressed || !selectionActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const mousePos = getMousePosition(event);
|
|
||||||
|
|
||||||
if (!mousePos) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserSelection(mousePos);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseUp = () => unsetUserSelection();
|
|
||||||
|
|
||||||
const onMouseLeave = () => {
|
|
||||||
unsetUserSelection();
|
|
||||||
unsetNodesSelection();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="react-flow__selectionpane react-flow__container"
|
className="react-flow__selectionpane react-flow__container"
|
||||||
@@ -108,7 +149,16 @@ export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
|
|||||||
onMouseUp={onMouseUp}
|
onMouseUp={onMouseUp}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
>
|
>
|
||||||
<SelectionRect />
|
{userSelectionRect.draw && (
|
||||||
|
<div
|
||||||
|
className="react-flow__selection react-flow__container"
|
||||||
|
style={{
|
||||||
|
width: userSelectionRect.width,
|
||||||
|
height: userSelectionRect.height,
|
||||||
|
transform: `translate(${userSelectionRect.x}px, ${userSelectionRect.y}px)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,13 +88,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
|||||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source);
|
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source);
|
||||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals, edge.target);
|
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals, edge.target);
|
||||||
|
|
||||||
if (!sourceIsValid) {
|
if (!sourceIsValid || !targetIsValid) {
|
||||||
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetIsValid) {
|
|
||||||
console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface FlowRendererProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
unsetNodesSelection: s.unsetNodesSelection,
|
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||||
resetSelectedElements: s.resetSelectedElements,
|
resetSelectedElements: s.resetSelectedElements,
|
||||||
nodesSelectionActive: s.nodesSelectionActive,
|
nodesSelectionActive: s.nodesSelectionActive,
|
||||||
});
|
});
|
||||||
@@ -62,7 +62,7 @@ const FlowRenderer = ({
|
|||||||
noWheelClassName,
|
noWheelClassName,
|
||||||
noPanClassName,
|
noPanClassName,
|
||||||
}: FlowRendererProps) => {
|
}: FlowRendererProps) => {
|
||||||
const { unsetNodesSelection, resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow);
|
const { setNodesSelectionActive, resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow);
|
||||||
|
|
||||||
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ const FlowRenderer = ({
|
|||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
onPaneClick?.(event);
|
onPaneClick?.(event);
|
||||||
unsetNodesSelection();
|
setNodesSelectionActive(false);
|
||||||
resetSelectedElements();
|
resetSelectedElements();
|
||||||
},
|
},
|
||||||
[onPaneClick]
|
[onPaneClick]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { memo, useMemo, ComponentType, MouseEvent, useEffect, useRef } from 'react';
|
import React, { memo, useMemo, ComponentType, MouseEvent, useEffect, useRef } from 'react';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
import useVisibleNodes from '../../hooks/useVisibleNodes';
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
|
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
|
||||||
@@ -43,6 +44,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
snapToGrid,
|
snapToGrid,
|
||||||
nodeInternals,
|
nodeInternals,
|
||||||
} = useStore(selector, shallow);
|
} = useStore(selector, shallow);
|
||||||
|
const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
|
||||||
const reseizeObserverRef = useRef<ResizeObserver>();
|
const reseizeObserverRef = useRef<ResizeObserver>();
|
||||||
|
|
||||||
const resizeObserver = useMemo(() => {
|
const resizeObserver = useMemo(() => {
|
||||||
@@ -73,7 +75,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="react-flow__nodes react-flow__container">
|
<div className="react-flow__nodes react-flow__container">
|
||||||
{Array.from(nodeInternals).map(([_, node]) => {
|
{nodes.map((node) => {
|
||||||
const nodeType = node.type || 'default';
|
const nodeType = node.type || 'default';
|
||||||
const internals = nodeInternals.get(node.id);
|
const internals = nodeInternals.get(node.id);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface HookParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
unsetNodesSelection: s.unsetNodesSelection,
|
setNodesSelectionActive: s.setNodesSelectionActive,
|
||||||
setMultiSelectionActive: s.setMultiSelectionActive,
|
setMultiSelectionActive: s.setMultiSelectionActive,
|
||||||
resetSelectedElements: s.resetSelectedElements,
|
resetSelectedElements: s.resetSelectedElements,
|
||||||
onNodesChange: s.onNodesChange,
|
onNodesChange: s.onNodesChange,
|
||||||
@@ -21,7 +21,7 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
|
|
||||||
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { unsetNodesSelection, setMultiSelectionActive, resetSelectedElements, onNodesChange, onEdgesChange } =
|
const { setNodesSelectionActive, setMultiSelectionActive, resetSelectedElements, onNodesChange, onEdgesChange } =
|
||||||
useStore(selector, shallow);
|
useStore(selector, shallow);
|
||||||
|
|
||||||
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
||||||
@@ -46,7 +46,7 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
|||||||
onNodesChange?.(nodeChanges);
|
onNodesChange?.(nodeChanges);
|
||||||
onEdgesChange?.(edgeChanges);
|
onEdgesChange?.(edgeChanges);
|
||||||
|
|
||||||
unsetNodesSelection();
|
setNodesSelectionActive(false);
|
||||||
resetSelectedElements();
|
resetSelectedElements();
|
||||||
}
|
}
|
||||||
}, [deleteKeyPressed, onNodesChange, onEdgesChange]);
|
}, [deleteKeyPressed, onNodesChange, onEdgesChange]);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useState, useCallback, SetStateAction, Dispatch } from 'react';
|
||||||
|
|
||||||
|
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
||||||
|
import { Node, NodeChange, Edge, EdgeChange } from '../types';
|
||||||
|
|
||||||
|
type ApplyChanges<ItemType, ChangesType> = (changes: ChangesType[], items: ItemType[]) => ItemType[];
|
||||||
|
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||||
|
|
||||||
|
// returns a hook that can be used liked this:
|
||||||
|
// const [nodes, setNodes, onNodesChange] = useNodesState(intialNodes);
|
||||||
|
function createUseItemsState<ItemType, ChangesType>(
|
||||||
|
applyChangesFunction: ApplyChanges<ItemType, ChangesType>
|
||||||
|
): (initialItems: ItemType[]) => [ItemType[], Dispatch<SetStateAction<ItemType[]>>, OnChange<ChangesType>] {
|
||||||
|
return (initialItems: ItemType[]) => {
|
||||||
|
const [items, setItems] = useState<ItemType[]>(initialItems);
|
||||||
|
|
||||||
|
const onItemsChange = useCallback(
|
||||||
|
(changes: ChangesType[]) => setItems((items) => applyChangesFunction(changes, items)),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [items, setItems, onItemsChange];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNodesState = createUseItemsState<Node, NodeChange>(applyNodeChanges as ApplyChanges<Node, NodeChange>);
|
||||||
|
export const useEdgesState = createUseItemsState<Edge, EdgeChange>(applyEdgeChanges as ApplyChanges<Edge, EdgeChange>);
|
||||||
@@ -6,7 +6,7 @@ import { ReactFlowState } from '../types';
|
|||||||
|
|
||||||
const updateSizeSelector = (state: ReactFlowState) => state.updateSize;
|
const updateSizeSelector = (state: ReactFlowState) => state.updateSize;
|
||||||
|
|
||||||
function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>) {
|
function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>): void {
|
||||||
const updateSize = useStore(updateSizeSelector);
|
const updateSize = useStore(updateSizeSelector);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ function useVisibleNodes(onlyRenderVisible: boolean) {
|
|||||||
const nodes = useStore(
|
const nodes = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(s: ReactFlowState) => {
|
(s: ReactFlowState) => {
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
|
||||||
const nodes = Array.from(s.nodeInternals).map(([_, node]) => node);
|
|
||||||
return onlyRenderVisible
|
return onlyRenderVisible
|
||||||
? getNodesInside(nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
|
? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
|
||||||
: nodes;
|
: Array.from(s.nodeInternals).map(([_, node]) => node);
|
||||||
},
|
},
|
||||||
[onlyRenderVisible]
|
[onlyRenderVisible]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { zoomIdentity } from 'd3-zoom';
|
|||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
import { useStoreApi, useStore } from '../store';
|
import { useStoreApi, useStore } from '../store';
|
||||||
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
|
import { getRectOfNodeInternals, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
|
||||||
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types';
|
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types';
|
||||||
|
|
||||||
const DEFAULT_PADDING = 0.1;
|
const DEFAULT_PADDING = 0.1;
|
||||||
@@ -48,7 +48,9 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden));
|
const bounds = getRectOfNodeInternals(
|
||||||
|
options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
|
||||||
|
);
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
const [x, y, zoom] = getTransformForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
width,
|
width,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
|
|||||||
|
|
||||||
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
|
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
|
||||||
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
|
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
|
||||||
|
export * from './hooks/useNodesEdgesState';
|
||||||
|
|
||||||
export * from './additional-components';
|
export * from './additional-components';
|
||||||
export { useStore, useStoreApi } from './store';
|
export { useStore, useStoreApi } from './store';
|
||||||
|
|||||||
+43
-180
@@ -25,64 +25,14 @@ import {
|
|||||||
Transform,
|
Transform,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
ReactFlowStore,
|
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
|
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
import { createNodeInternals, createNodeOrEdgeSelectionChange } from './utils';
|
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
|
import { createNodeInternals, createPositionChange, isParentSelected } from './utils';
|
||||||
|
import initialState from './initialState';
|
||||||
|
|
||||||
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
||||||
|
|
||||||
const infiniteExtent: CoordinateExtent = [
|
|
||||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
|
||||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
|
||||||
];
|
|
||||||
|
|
||||||
const initialState: ReactFlowStore = {
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
transform: [0, 0, 1],
|
|
||||||
nodeInternals: new Map(),
|
|
||||||
edges: [],
|
|
||||||
onNodesChange: null,
|
|
||||||
onEdgesChange: null,
|
|
||||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
|
||||||
d3Zoom: null,
|
|
||||||
d3Selection: null,
|
|
||||||
d3ZoomHandler: undefined,
|
|
||||||
minZoom: 0.5,
|
|
||||||
maxZoom: 2,
|
|
||||||
translateExtent: infiniteExtent,
|
|
||||||
nodeExtent: infiniteExtent,
|
|
||||||
nodesSelectionActive: false,
|
|
||||||
selectionActive: false,
|
|
||||||
userSelectionRect: {
|
|
||||||
startX: 0,
|
|
||||||
startY: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
draw: false,
|
|
||||||
},
|
|
||||||
connectionNodeId: null,
|
|
||||||
connectionHandleId: null,
|
|
||||||
connectionHandleType: 'source',
|
|
||||||
connectionPosition: { x: 0, y: 0 },
|
|
||||||
connectionMode: ConnectionMode.Strict,
|
|
||||||
|
|
||||||
snapGrid: [15, 15],
|
|
||||||
snapToGrid: false,
|
|
||||||
|
|
||||||
nodesDraggable: true,
|
|
||||||
nodesConnectable: true,
|
|
||||||
elementsSelectable: true,
|
|
||||||
|
|
||||||
multiSelectionActive: false,
|
|
||||||
|
|
||||||
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const createStore = () =>
|
const createStore = () =>
|
||||||
create<ReactFlowState>((set, get) => ({
|
create<ReactFlowState>((set, get) => ({
|
||||||
...initialState,
|
...initialState,
|
||||||
@@ -98,7 +48,7 @@ const createStore = () =>
|
|||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
||||||
const { onNodesChange, transform, nodeInternals } = get();
|
const { onNodesChange, transform, nodeInternals } = get();
|
||||||
|
|
||||||
const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
|
const changes: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
|
||||||
const node = nodeInternals.get(update.id);
|
const node = nodeInternals.get(update.id);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
@@ -117,12 +67,11 @@ const createStore = () =>
|
|||||||
...dimensions,
|
...dimensions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const change = {
|
res.push({
|
||||||
id: node.id,
|
id: node.id,
|
||||||
type: 'dimensions',
|
type: 'dimensions',
|
||||||
dimensions,
|
dimensions,
|
||||||
} as NodeChange;
|
} as NodeChange);
|
||||||
res.push(change);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,146 +80,59 @@ const createStore = () =>
|
|||||||
|
|
||||||
set({ nodeInternals: new Map(nodeInternals) });
|
set({ nodeInternals: new Map(nodeInternals) });
|
||||||
|
|
||||||
if (nodesToChange?.length > 0) {
|
if (changes?.length > 0) {
|
||||||
onNodesChange?.(nodesToChange);
|
onNodesChange?.(changes);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => {
|
updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => {
|
||||||
const { onNodesChange, nodeExtent, nodeInternals } = get();
|
const { onNodesChange, nodeExtent, nodeInternals } = get();
|
||||||
|
|
||||||
if (onNodesChange) {
|
if (onNodesChange) {
|
||||||
const nodes = Array.from(nodeInternals);
|
const changes: NodeDimensionChange[] = [];
|
||||||
const matchingNodes = nodes.filter(([_, n]) => !!(n.selected || n.id === id));
|
|
||||||
if (matchingNodes?.length) {
|
|
||||||
onNodesChange(
|
|
||||||
matchingNodes?.map(([_, node]) => {
|
|
||||||
const change: NodeDimensionChange = {
|
|
||||||
id: node.id,
|
|
||||||
type: 'dimensions',
|
|
||||||
dragging: !!dragging,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (diff) {
|
nodeInternals.forEach((node) => {
|
||||||
let currentExtent = nodeExtent || node.extent;
|
if (node.selected) {
|
||||||
|
if (!node.parentNode) {
|
||||||
|
changes.push(createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals }));
|
||||||
|
} else if (!isParentSelected(node, nodeInternals)) {
|
||||||
|
changes.push(createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals }));
|
||||||
|
}
|
||||||
|
} else if (node.id === id) {
|
||||||
|
changes.push(createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (node.extent === 'parent' && node.parentNode && node.width && node.height) {
|
if (changes?.length) {
|
||||||
const parent = nodeInternals.get(node.parentNode);
|
onNodesChange(changes);
|
||||||
currentExtent =
|
|
||||||
parent?.width && parent?.height
|
|
||||||
? [
|
|
||||||
[0, 0],
|
|
||||||
[parent.width - node.width, parent.height - node.height],
|
|
||||||
]
|
|
||||||
: currentExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
change.position = currentExtent
|
|
||||||
? clampPosition(
|
|
||||||
{
|
|
||||||
x: node.position.x + diff.x,
|
|
||||||
y: node.position.y + diff.y,
|
|
||||||
},
|
|
||||||
currentExtent
|
|
||||||
)
|
|
||||||
: { x: node.position.x + diff.x, y: node.position.y + diff.y };
|
|
||||||
}
|
|
||||||
|
|
||||||
return change;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setUserSelection: (mousePos: XYPosition) => {
|
addSelectedNodes: (selectedNodeIds: string[]) => {
|
||||||
set({
|
const { multiSelectionActive, onNodesChange, nodeInternals } = get();
|
||||||
selectionActive: true,
|
|
||||||
userSelectionRect: {
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
startX: mousePos.x,
|
|
||||||
startY: mousePos.y,
|
|
||||||
x: mousePos.x,
|
|
||||||
y: mousePos.y,
|
|
||||||
draw: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateUserSelection: (mousePos: XYPosition) => {
|
|
||||||
const { userSelectionRect, nodeInternals, edges, transform, onNodesChange, onEdgesChange } = get();
|
|
||||||
const startX = userSelectionRect.startX ?? 0;
|
|
||||||
const startY = userSelectionRect.startY ?? 0;
|
|
||||||
|
|
||||||
const nextUserSelectRect = {
|
|
||||||
...userSelectionRect,
|
|
||||||
x: mousePos.x < startX ? mousePos.x : userSelectionRect.x,
|
|
||||||
y: mousePos.y < startY ? mousePos.y : userSelectionRect.y,
|
|
||||||
width: Math.abs(mousePos.x - startX),
|
|
||||||
height: Math.abs(mousePos.y - startY),
|
|
||||||
};
|
|
||||||
|
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
|
||||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
|
||||||
const selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true);
|
|
||||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
|
||||||
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
|
||||||
|
|
||||||
onNodesChange?.(
|
|
||||||
nodes.map((n) => createNodeOrEdgeSelectionChange(selectedNodeIds.includes(n.id))(n)) as NodeChange[]
|
|
||||||
);
|
|
||||||
onEdgesChange?.(
|
|
||||||
edges.map((e) => createNodeOrEdgeSelectionChange(selectedEdgeIds.includes(e.id))(e)) as EdgeChange[]
|
|
||||||
);
|
|
||||||
|
|
||||||
set({
|
|
||||||
userSelectionRect: nextUserSelectRect,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unsetUserSelection: () => {
|
|
||||||
const { userSelectionRect, nodeInternals } = get();
|
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
|
||||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
|
||||||
const selectedNodes = nodes.filter((node) => node.selected);
|
|
||||||
|
|
||||||
const stateUpdate = {
|
|
||||||
selectionActive: false,
|
|
||||||
userSelectionRect: {
|
|
||||||
...userSelectionRect,
|
|
||||||
draw: false,
|
|
||||||
},
|
|
||||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
|
||||||
nodesSelectionActive: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (selectedNodes && selectedNodes.length > 0) {
|
|
||||||
const selectedNodesBbox = getRectOfNodes(selectedNodes);
|
|
||||||
stateUpdate.selectedNodesBbox = selectedNodesBbox;
|
|
||||||
stateUpdate.nodesSelectionActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
set(stateUpdate);
|
|
||||||
},
|
|
||||||
addSelectedElements: (selectedElementsArr: Array<Node | Edge>) => {
|
|
||||||
const { multiSelectionActive, onNodesChange, onEdgesChange, nodeInternals, edges } = get();
|
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
// @TODO: work with nodeInternals instead of converting it to an array
|
||||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||||
let changedNodes;
|
let changedNodes;
|
||||||
let changedEdges;
|
|
||||||
|
|
||||||
if (multiSelectionActive) {
|
if (multiSelectionActive) {
|
||||||
changedNodes = selectedElementsArr.filter(isNode).map(createNodeOrEdgeSelectionChange(true));
|
changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
||||||
changedEdges = selectedElementsArr.filter(isEdge).map(createNodeOrEdgeSelectionChange(true));
|
|
||||||
} else {
|
} else {
|
||||||
changedNodes = nodes.map((node) =>
|
changedNodes = getSelectionChanges(nodes, selectedNodeIds);
|
||||||
createNodeOrEdgeSelectionChange(selectedElementsArr.some((e) => e.id === node.id))(node)
|
|
||||||
);
|
|
||||||
changedEdges = edges.map((edge) =>
|
|
||||||
createNodeOrEdgeSelectionChange(selectedElementsArr.some((e) => e.id === edge.id))(edge)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedNodes.length) {
|
if (changedNodes.length) {
|
||||||
onNodesChange?.(changedNodes as NodeChange[]);
|
onNodesChange?.(changedNodes as NodeChange[]);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
addSelectedEdges: (selectedEdgeIds: string[]) => {
|
||||||
|
const { multiSelectionActive, onEdgesChange, edges } = get();
|
||||||
|
|
||||||
|
let changedEdges;
|
||||||
|
|
||||||
|
if (multiSelectionActive) {
|
||||||
|
changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
||||||
|
} else {
|
||||||
|
changedEdges = getSelectionChanges(edges, selectedEdgeIds);
|
||||||
|
}
|
||||||
|
|
||||||
if (changedEdges.length) {
|
if (changedEdges.length) {
|
||||||
onEdgesChange?.(changedEdges as EdgeChange[]);
|
onEdgesChange?.(changedEdges as EdgeChange[]);
|
||||||
@@ -283,9 +145,9 @@ const createStore = () =>
|
|||||||
|
|
||||||
const nodesToUnselect = nodes.map((n) => {
|
const nodesToUnselect = nodes.map((n) => {
|
||||||
n.selected = false;
|
n.selected = false;
|
||||||
return createNodeOrEdgeSelectionChange(false)(n);
|
return createSelectionChange(n.id, false);
|
||||||
}) as NodeChange[];
|
}) as NodeChange[];
|
||||||
const edgesToUnselect = edges.map(createNodeOrEdgeSelectionChange(false)) as EdgeChange[];
|
const edgesToUnselect = edges.map((edge) => createSelectionChange(edge.id, false)) as EdgeChange[];
|
||||||
|
|
||||||
if (nodesToUnselect.length) {
|
if (nodesToUnselect.length) {
|
||||||
onNodesChange?.(nodesToUnselect);
|
onNodesChange?.(nodesToUnselect);
|
||||||
@@ -324,8 +186,8 @@ const createStore = () =>
|
|||||||
const { nodeInternals, edges, onNodesChange, onEdgesChange } = get();
|
const { nodeInternals, edges, onNodesChange, onEdgesChange } = get();
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
// @TODO: work with nodeInternals instead of converting it to an array
|
||||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||||
const nodesToUnselect = nodes.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false));
|
const nodesToUnselect = nodes.filter((e) => e.selected).map((n) => createSelectionChange(n.id, false));
|
||||||
const edgesToUnselect = edges.filter((e) => e.selected).map(createNodeOrEdgeSelectionChange(false));
|
const edgesToUnselect = edges.filter((e) => e.selected).map((e) => createSelectionChange(e.id, false));
|
||||||
|
|
||||||
if (nodesToUnselect.length) {
|
if (nodesToUnselect.length) {
|
||||||
onNodesChange?.(nodesToUnselect as NodeChange[]);
|
onNodesChange?.(nodesToUnselect as NodeChange[]);
|
||||||
@@ -346,7 +208,8 @@ const createStore = () =>
|
|||||||
nodeInternals: new Map(nodeInternals),
|
nodeInternals: new Map(nodeInternals),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unsetNodesSelection: () => set({ nodesSelectionActive: false }),
|
setUserSelectionActive: (userSelectionActive: boolean) => set({ userSelectionActive }),
|
||||||
|
setNodesSelectionActive: (nodesSelectionActive: boolean) => set({ nodesSelectionActive }),
|
||||||
updateTransform: (transform: Transform) => set({ transform }),
|
updateTransform: (transform: Transform) => set({ transform }),
|
||||||
updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }),
|
updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }),
|
||||||
setOnConnect: (onConnect: OnConnect) => set({ onConnect }),
|
setOnConnect: (onConnect: OnConnect) => set({ onConnect }),
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { CoordinateExtent, ReactFlowStore, ConnectionMode } from '../types';
|
||||||
|
|
||||||
|
const infiniteExtent: CoordinateExtent = [
|
||||||
|
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||||
|
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialState: ReactFlowStore = {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
transform: [0, 0, 1],
|
||||||
|
nodeInternals: new Map(),
|
||||||
|
edges: [],
|
||||||
|
onNodesChange: null,
|
||||||
|
onEdgesChange: null,
|
||||||
|
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
||||||
|
d3Zoom: null,
|
||||||
|
d3Selection: null,
|
||||||
|
d3ZoomHandler: undefined,
|
||||||
|
minZoom: 0.5,
|
||||||
|
maxZoom: 2,
|
||||||
|
translateExtent: infiniteExtent,
|
||||||
|
nodeExtent: infiniteExtent,
|
||||||
|
nodesSelectionActive: false,
|
||||||
|
userSelectionActive: false,
|
||||||
|
connectionNodeId: null,
|
||||||
|
connectionHandleId: null,
|
||||||
|
connectionHandleType: 'source',
|
||||||
|
connectionPosition: { x: 0, y: 0 },
|
||||||
|
connectionMode: ConnectionMode.Strict,
|
||||||
|
|
||||||
|
snapGrid: [15, 15],
|
||||||
|
snapToGrid: false,
|
||||||
|
|
||||||
|
nodesDraggable: true,
|
||||||
|
nodesConnectable: true,
|
||||||
|
elementsSelectable: true,
|
||||||
|
|
||||||
|
multiSelectionActive: false,
|
||||||
|
|
||||||
|
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initialState;
|
||||||
+69
-7
@@ -1,5 +1,13 @@
|
|||||||
import { Node, Edge, NodeInternals, NodeInternalsItem, XYZPosition } from '../types';
|
import {
|
||||||
import { isNumeric } from '../utils';
|
CoordinateExtent,
|
||||||
|
Node,
|
||||||
|
NodeDimensionChange,
|
||||||
|
NodeInternals,
|
||||||
|
NodeInternalsItem,
|
||||||
|
XYPosition,
|
||||||
|
XYZPosition,
|
||||||
|
} from '../types';
|
||||||
|
import { clampPosition, isNumeric } from '../utils';
|
||||||
|
|
||||||
type ParentNodes = Record<string, boolean>;
|
type ParentNodes = Record<string, boolean>;
|
||||||
|
|
||||||
@@ -105,8 +113,62 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
|
|||||||
return nextNodeInternals;
|
return nextNodeInternals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createNodeOrEdgeSelectionChange = (selected: boolean) => (item: Node | Edge) => ({
|
export function isParentSelected(node: NodeInternalsItem, nodeInternals: NodeInternals): boolean {
|
||||||
id: item.id,
|
if (!node.parentNode) {
|
||||||
type: 'select',
|
return false;
|
||||||
selected,
|
}
|
||||||
});
|
|
||||||
|
const parentNode = nodeInternals.get(node.parentNode);
|
||||||
|
|
||||||
|
if (!parentNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentNode.selected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isParentSelected(parentNode, nodeInternals);
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePostiionChangeParams = {
|
||||||
|
node: NodeInternalsItem;
|
||||||
|
nodeExtent: CoordinateExtent;
|
||||||
|
nodeInternals: NodeInternals;
|
||||||
|
diff?: XYPosition;
|
||||||
|
dragging?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createPositionChange({
|
||||||
|
node,
|
||||||
|
diff,
|
||||||
|
dragging,
|
||||||
|
nodeExtent,
|
||||||
|
nodeInternals,
|
||||||
|
}: CreatePostiionChangeParams): NodeDimensionChange {
|
||||||
|
const change: NodeDimensionChange = {
|
||||||
|
id: node.id,
|
||||||
|
type: 'dimensions',
|
||||||
|
dragging: !!dragging,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (diff) {
|
||||||
|
const nextPosition = { x: node.position.x + diff.x, y: node.position.y + diff.y };
|
||||||
|
let currentExtent = nodeExtent || node.extent;
|
||||||
|
|
||||||
|
if (node.extent === 'parent' && 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
change.position = currentExtent ? clampPosition(nextPosition, currentExtent) : nextPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|||||||
+5
-14
@@ -24,12 +24,6 @@ export type OnNodesChange = (nodes: NodeChange[]) => void;
|
|||||||
|
|
||||||
export type OnEdgesChange = (nodes: EdgeChange[]) => void;
|
export type OnEdgesChange = (nodes: EdgeChange[]) => void;
|
||||||
|
|
||||||
export interface SelectionRect extends Rect {
|
|
||||||
startX: number;
|
|
||||||
startY: number;
|
|
||||||
draw: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OnLoadParams<T = any> = {
|
export type OnLoadParams<T = any> = {
|
||||||
zoomIn: () => void;
|
zoomIn: () => void;
|
||||||
zoomOut: () => void;
|
zoomOut: () => void;
|
||||||
@@ -149,9 +143,7 @@ export type ReactFlowStore = {
|
|||||||
nodeExtent: CoordinateExtent;
|
nodeExtent: CoordinateExtent;
|
||||||
|
|
||||||
nodesSelectionActive: boolean;
|
nodesSelectionActive: boolean;
|
||||||
selectionActive: boolean;
|
userSelectionActive: boolean;
|
||||||
|
|
||||||
userSelectionRect: SelectionRect;
|
|
||||||
|
|
||||||
connectionNodeId: string | null;
|
connectionNodeId: string | null;
|
||||||
connectionHandleId: string | null;
|
connectionHandleId: string | null;
|
||||||
@@ -176,13 +168,12 @@ export type ReactFlowActions = {
|
|||||||
setEdges: (edges: Edge[]) => void;
|
setEdges: (edges: Edge[]) => void;
|
||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||||
updateNodePosition: (update: NodeDiffUpdate) => void;
|
updateNodePosition: (update: NodeDiffUpdate) => void;
|
||||||
setUserSelection: (mousePos: XYPosition) => void;
|
setNodesSelectionActive: (isActive: boolean) => void;
|
||||||
updateUserSelection: (mousePos: XYPosition) => void;
|
setUserSelectionActive: (isActive: boolean) => void;
|
||||||
unsetUserSelection: () => void;
|
|
||||||
unsetNodesSelection: () => void;
|
|
||||||
resetSelectedElements: () => void;
|
resetSelectedElements: () => void;
|
||||||
unselectNodesAndEdges: () => void;
|
unselectNodesAndEdges: () => void;
|
||||||
addSelectedElements: (elements: Elements) => void;
|
addSelectedNodes: (nodeIds: string[]) => void;
|
||||||
|
addSelectedEdges: (edgeIds: string[]) => void;
|
||||||
updateTransform: (transform: Transform) => void;
|
updateTransform: (transform: Transform) => void;
|
||||||
updateSize: (size: Dimensions) => void;
|
updateSize: (size: Dimensions) => void;
|
||||||
initD3Zoom: (payload: InitD3ZoomPayload) => void;
|
initD3Zoom: (payload: InitD3ZoomPayload) => void;
|
||||||
|
|||||||
+2
-2
@@ -104,9 +104,9 @@ export type NodeDimensionUpdate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type NodeInternalsItem = Node & {
|
export type NodeInternalsItem = Node & {
|
||||||
positionAbsolute?: XYPosition;
|
positionAbsolute: XYPosition;
|
||||||
handleBounds?: NodeHandleBounds;
|
|
||||||
z: number;
|
z: number;
|
||||||
|
handleBounds?: NodeHandleBounds;
|
||||||
isParent?: boolean;
|
isParent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,3 +49,23 @@ export function applyNodeChanges(changes: NodeChange[], nodes: Node[]): Node[] {
|
|||||||
export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] {
|
export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] {
|
||||||
return applyChanges(changes, edges) as Edge[];
|
return applyChanges(changes, edges) as Edge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createSelectionChange = (id: string, selected: boolean) => ({
|
||||||
|
id,
|
||||||
|
type: 'select',
|
||||||
|
selected,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getSelectionChanges(items: any[], selectedIds: string[]) {
|
||||||
|
return items.reduce((res, item) => {
|
||||||
|
const willBeSelected = selectedIds.includes(item.id);
|
||||||
|
|
||||||
|
if (!item.selected && willBeSelected) {
|
||||||
|
res.push(createSelectionChange(item.id, true));
|
||||||
|
} else if (item.selected && !willBeSelected) {
|
||||||
|
res.push(createSelectionChange(item.id, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|||||||
+39
-21
@@ -1,6 +1,16 @@
|
|||||||
import { boxToRect, clamp, getBoundsOfBoxes, rectToBox } from '../utils';
|
import { boxToRect, clamp, getBoundsOfBoxes, rectToBox } from '../utils';
|
||||||
|
|
||||||
import { Node, Edge, Connection, EdgeMarkerType, Transform, XYPosition, Rect } from '../types';
|
import {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Connection,
|
||||||
|
EdgeMarkerType,
|
||||||
|
Transform,
|
||||||
|
XYPosition,
|
||||||
|
Rect,
|
||||||
|
NodeInternals,
|
||||||
|
NodeInternalsItem,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
export const isEdge = (element: Node | Connection | Edge): element is Edge =>
|
export const isEdge = (element: Node | Connection | Edge): element is Edge =>
|
||||||
'id' in element && 'source' in element && 'target' in element;
|
'id' in element && 'source' in element && 'target' in element;
|
||||||
@@ -124,6 +134,7 @@ export const pointToRendererPoint = (
|
|||||||
return position;
|
return position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @TODO: use one function for getRectOfNodes and getRectOfNodeInternals
|
||||||
export const getRectOfNodes = (nodes: Node[]): Rect => {
|
export const getRectOfNodes = (nodes: Node[]): Rect => {
|
||||||
const box = nodes.reduce(
|
const box = nodes.reduce(
|
||||||
(currBox, { position, width, height }) =>
|
(currBox, { position, width, height }) =>
|
||||||
@@ -134,14 +145,24 @@ export const getRectOfNodes = (nodes: Node[]): Rect => {
|
|||||||
return boxToRect(box);
|
return boxToRect(box);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getRectOfNodeInternals = (nodes: NodeInternalsItem[]): Rect => {
|
||||||
|
const box = nodes.reduce(
|
||||||
|
(currBox, { positionAbsolute, width, height }) =>
|
||||||
|
getBoundsOfBoxes(currBox, rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 })),
|
||||||
|
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }
|
||||||
|
);
|
||||||
|
|
||||||
|
return boxToRect(box);
|
||||||
|
};
|
||||||
|
|
||||||
export const getNodesInside = (
|
export const getNodesInside = (
|
||||||
nodes: Node[],
|
nodeInternals: NodeInternals,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
[tx, ty, tScale]: Transform = [0, 0, 1],
|
[tx, ty, tScale]: Transform = [0, 0, 1],
|
||||||
partially: boolean = false,
|
partially: boolean = false,
|
||||||
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
|
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
|
||||||
excludeNonSelectableNodes: boolean = false
|
excludeNonSelectableNodes: boolean = false
|
||||||
): Node[] => {
|
): NodeInternalsItem[] => {
|
||||||
const rBox = rectToBox({
|
const rBox = rectToBox({
|
||||||
x: (rect.x - tx) / tScale,
|
x: (rect.x - tx) / tScale,
|
||||||
y: (rect.y - ty) / tScale,
|
y: (rect.y - ty) / tScale,
|
||||||
@@ -149,35 +170,32 @@ export const getNodesInside = (
|
|||||||
height: rect.height / tScale,
|
height: rect.height / tScale,
|
||||||
});
|
});
|
||||||
|
|
||||||
return nodes.filter(({ selectable = true, position, width, height, dragging }) => {
|
const visibleNodes: NodeInternalsItem[] = [];
|
||||||
|
|
||||||
|
nodeInternals.forEach((node) => {
|
||||||
|
const { positionAbsolute, width, height, dragging, selectable = true } = node;
|
||||||
|
|
||||||
if (excludeNonSelectableNodes && !selectable) {
|
if (excludeNonSelectableNodes && !selectable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nBox = rectToBox({ ...position, width: width || 0, height: height || 0 });
|
const nBox = rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 });
|
||||||
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
|
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
|
||||||
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
||||||
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
||||||
|
const notInitialized =
|
||||||
|
typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null || dragging;
|
||||||
|
|
||||||
if (
|
const partiallyVisible = partially && overlappingArea > 0;
|
||||||
typeof width === 'undefined' ||
|
|
||||||
typeof height === 'undefined' ||
|
|
||||||
width === null ||
|
|
||||||
height === null ||
|
|
||||||
dragging
|
|
||||||
) {
|
|
||||||
// nodes are initialized with width and height = null
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partially) {
|
|
||||||
return overlappingArea > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const area = (width || 0) * (height || 0);
|
const area = (width || 0) * (height || 0);
|
||||||
|
const isVisible = notInitialized || partiallyVisible || overlappingArea >= area;
|
||||||
|
|
||||||
return overlappingArea >= area;
|
if (isVisible) {
|
||||||
|
visibleNodes.push(node);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return visibleNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
|
export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
|
||||||
|
|||||||
Reference in New Issue
Block a user