Merge pull request #2238 from wbkd/v10.3.8

v10.3.8
This commit is contained in:
Moritz Klack
2022-06-22 17:41:35 +02:00
committed by GitHub
18 changed files with 111 additions and 53 deletions
+20 -3
View File
@@ -8,6 +8,8 @@ import ReactFlow, {
Position, Position,
useNodesState, useNodesState,
useEdgesState, useEdgesState,
MarkerType,
EdgeMarker,
} from 'react-flow-renderer'; } from 'react-flow-renderer';
import dagre from 'dagre'; import dagre from 'dagre';
@@ -27,9 +29,12 @@ const LayoutFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialItems.nodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialItems.nodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialItems.edges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialItems.edges);
const onConnect = useCallback((connection: Connection) => { const onConnect = useCallback(
setEdges((eds) => addEdge(connection, eds)); (connection: Connection) => {
}, []); setEdges((eds) => addEdge(connection, eds));
},
[setEdges]
);
const onLayout = (direction: string) => { const onLayout = (direction: string) => {
const isHorizontal = direction === 'LR'; const isHorizontal = direction === 'LR';
@@ -63,6 +68,17 @@ const LayoutFlow = () => {
setNodes((nds) => nds.map((n) => ({ ...n, selected: false }))); setNodes((nds) => nds.map((n) => ({ ...n, selected: false })));
}; };
const changeMarker = () => {
setEdges((eds) =>
eds.map((e) => ({
...e,
markerEnd: {
type: (e.markerEnd as EdgeMarker)?.type === MarkerType.Arrow ? MarkerType.ArrowClosed : MarkerType.Arrow,
},
}))
);
};
return ( return (
<div className="layoutflow"> <div className="layoutflow">
<ReactFlowProvider> <ReactFlowProvider>
@@ -85,6 +101,7 @@ const LayoutFlow = () => {
horizontal layout horizontal layout
</button> </button>
<button onClick={() => unselect()}>unselect nodes</button> <button onClick={() => unselect()}>unselect nodes</button>
<button onClick={() => changeMarker()}>change marker</button>
</div> </div>
</ReactFlowProvider> </ReactFlowProvider>
</div> </div>
+10 -10
View File
@@ -1,4 +1,4 @@
import { Node, Edge, XYPosition } from 'react-flow-renderer'; import { Node, Edge, XYPosition, MarkerType } from 'react-flow-renderer';
const position: XYPosition = { x: 0, y: 0 }; const position: XYPosition = { x: 0, y: 0 };
@@ -59,16 +59,16 @@ const nodes: Node[] = [
]; ];
const edges: Edge[] = [ const edges: Edge[] = [
{ id: 'e12', source: '1', target: '2', type: 'smoothstep' }, { id: 'e12', source: '1', target: '2', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e13', source: '1', target: '3', type: 'smoothstep' }, { id: 'e13', source: '1', target: '3', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e22a', source: '2', target: '2a', type: 'smoothstep' }, { id: 'e22a', source: '2', target: '2a', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e22b', source: '2', target: '2b', type: 'smoothstep' }, { id: 'e22b', source: '2', target: '2b', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e22c', source: '2', target: '2c', type: 'smoothstep' }, { id: 'e22c', source: '2', target: '2c', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep' }, { id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e45', source: '4', target: '5', type: 'smoothstep' }, { id: 'e45', source: '4', target: '5', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e56', source: '5', target: '6', type: 'smoothstep' }, { id: 'e56', source: '5', target: '6', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
{ id: 'e57', source: '5', target: '7', type: 'smoothstep' }, { id: 'e57', source: '5', target: '7', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
]; ];
const nodesAndEdges = { nodes, edges }; const nodesAndEdges = { nodes, edges };
+7 -5
View File
@@ -9,6 +9,7 @@ import ReactFlow, {
ReactFlowProvider, ReactFlowProvider,
useNodesState, useNodesState,
useEdgesState, useEdgesState,
MarkerType,
} from 'react-flow-renderer'; } from 'react-flow-renderer';
import './multiflows.css'; import './multiflows.css';
@@ -21,12 +22,12 @@ const initialNodes: Node[] = [
]; ];
const initialEdges: Edge[] = [ const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e1-2', source: '1', target: '2', animated: true, markerEnd: { type: MarkerType.Arrow } },
{ id: 'e1-3', source: '1', target: '3' }, { id: 'e1-3', source: '1', target: '3' },
]; ];
const Flow: FC = () => { const Flow: FC<{ id: string }> = ({ id }) => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, , onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = (params: Edge | Connection) => setEdges((eds) => addEdge(params, eds)); const onConnect = (params: Edge | Connection) => setEdges((eds) => addEdge(params, eds));
@@ -39,6 +40,7 @@ const Flow: FC = () => {
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}
id={id}
> >
<Background /> <Background />
</ReactFlow> </ReactFlow>
@@ -48,8 +50,8 @@ const Flow: FC = () => {
const MultiFlows: FC = () => ( const MultiFlows: FC = () => (
<div className="react-flow__example-multiflows"> <div className="react-flow__example-multiflows">
<Flow /> <Flow id="flow-a" />
<Flow /> <Flow id="flow-b" />
</div> </div>
); );
+13 -5
View File
@@ -20,7 +20,13 @@ const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
const initialNodes: Node[] = [ const initialNodes: Node[] = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' }, {
id: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
className: 'light',
},
{ {
id: '4', id: '4',
data: { label: 'Node 4' }, data: { label: 'Node 4' },
@@ -34,21 +40,23 @@ const initialNodes: Node[] = [
position: { x: 15, y: 15 }, position: { x: 15, y: 15 },
className: 'light', className: 'light',
parentNode: '4', parentNode: '4',
extent: 'parent', extent: [
[0, 0],
[100, 100],
],
}, },
{ {
id: '4b', id: '4b',
data: { label: 'Node 4b' }, data: { label: 'Node 4b' },
position: { x: 150, y: 50 }, position: { x: 100, y: 60 },
className: 'light', className: 'light',
style: { backgroundColor: 'rgba(50, 50, 255, 0.5)', height: 200, width: 300 }, style: { backgroundColor: 'rgba(50, 50, 255, 0.5)', height: 200, width: 300 },
parentNode: '4', parentNode: '4',
expandParent: true,
}, },
{ {
id: '4b1', id: '4b1',
data: { label: 'Node 4b1' }, data: { label: 'Node 4b1' },
position: { x: 20, y: 20 }, position: { x: 40, y: 20 },
className: 'light', className: 'light',
parentNode: '4b', parentNode: '4b',
}, },
+3 -2
View File
@@ -53,6 +53,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
onEdgeUpdateEnd, onEdgeUpdateEnd,
markerEnd, markerEnd,
markerStart, markerStart,
rfId,
}: WrapEdgeProps): JSX.Element | null => { }: WrapEdgeProps): JSX.Element | null => {
const [updating, setUpdating] = useState<boolean>(false); const [updating, setUpdating] = useState<boolean>(false);
const { addSelectedEdges, connectionMode } = useStore(selector, shallow); const { addSelectedEdges, connectionMode } = useStore(selector, shallow);
@@ -120,8 +121,8 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
const onEdgeUpdaterMouseEnter = () => setUpdating(true); const onEdgeUpdaterMouseEnter = () => setUpdating(true);
const onEdgeUpdaterMouseOut = () => setUpdating(false); const onEdgeUpdaterMouseOut = () => setUpdating(false);
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart)})`, [markerStart]); const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd)})`, [markerEnd]); const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
if (hidden) { if (hidden) {
return null; return null;
+2
View File
@@ -41,6 +41,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
isParent, isParent,
noPanClassName, noPanClassName,
noDragClassName, noDragClassName,
initialized,
}: WrapNodeProps) => { }: WrapNodeProps) => {
const store = useStoreApi(); const store = useStoreApi();
const updateNodeDimensions = useStore(selector); const updateNodeDimensions = useStore(selector);
@@ -134,6 +135,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
zIndex, zIndex,
transform: `translate(${xPos}px,${yPos}px)`, transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none', pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: initialized ? 'visible' : 'hidden',
...style, ...style,
}} }}
onMouseEnter={onMouseEnterHandler} onMouseEnter={onMouseEnterHandler}
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { memo, useCallback } from 'react';
import { useStore } from '../../store'; import { useStore } from '../../store';
import { EdgeMarker, ReactFlowState } from '../../types'; import { EdgeMarker, ReactFlowState } from '../../types';
@@ -9,6 +9,7 @@ interface MarkerProps extends EdgeMarker {
} }
interface MarkerDefinitionsProps { interface MarkerDefinitionsProps {
defaultColor: string; defaultColor: string;
rfId?: string;
} }
const Marker = ({ const Marker = ({
@@ -40,26 +41,36 @@ const Marker = ({
); );
}; };
const edgesSelector = (s: ReactFlowState) => s.edges; const markerSelector =
({ defaultColor, rfId }: { defaultColor: string; rfId?: string }) =>
const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => { (s: ReactFlowState) => {
const edges = useStore(edgesSelector);
const markers = useMemo(() => {
const ids: string[] = []; const ids: string[] = [];
return edges.reduce<MarkerProps[]>((markers, edge) => { return s.edges
[edge.markerStart, edge.markerEnd].forEach((marker) => { .reduce<MarkerProps[]>((markers, edge) => {
if (marker && typeof marker === 'object') { [edge.markerStart, edge.markerEnd].forEach((marker) => {
const markerId = getMarkerId(marker); if (marker && typeof marker === 'object') {
if (!ids.includes(markerId)) { const markerId = getMarkerId(marker, rfId);
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker }); if (!ids.includes(markerId)) {
ids.push(markerId); markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
ids.push(markerId);
}
} }
} });
}); return markers;
return markers.sort((a, b) => a.id.localeCompare(b.id)); }, [])
}, []); .sort((a, b) => a.id.localeCompare(b.id));
}, [edges, defaultColor]); };
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
// that we can then use for creating our unique marker ids
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
const markers = useStore(
useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]),
// the id includes all marker options, so we just need to look at that part of the marker
(a, b) => !(a.length !== b.length || a.some((m, i) => m.id !== b[i].id))
);
return ( return (
<defs> <defs>
@@ -82,4 +93,4 @@ const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => {
MarkerDefinitions.displayName = 'MarkerDefinitions'; MarkerDefinitions.displayName = 'MarkerDefinitions';
export default MarkerDefinitions; export default memo(MarkerDefinitions);
+3 -1
View File
@@ -40,6 +40,7 @@ interface EdgeRendererProps {
edgeUpdaterRadius?: number; edgeUpdaterRadius?: number;
noPanClassName?: string; noPanClassName?: string;
elevateEdgesOnSelect: boolean; elevateEdgesOnSelect: boolean;
rfId?: string;
} }
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
@@ -93,7 +94,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
height={height} height={height}
className="react-flow__edges react-flow__container" className="react-flow__edges react-flow__container"
> >
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} />} {isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={props.rfId} />}
<g> <g>
{edges.map((edge: Edge) => { {edges.map((edge: Edge) => {
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source); const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source);
@@ -184,6 +185,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
onEdgeDoubleClick={props.onEdgeDoubleClick} onEdgeDoubleClick={props.onEdgeDoubleClick}
onEdgeUpdateStart={props.onEdgeUpdateStart} onEdgeUpdateStart={props.onEdgeUpdateStart}
onEdgeUpdateEnd={props.onEdgeUpdateEnd} onEdgeUpdateEnd={props.onEdgeUpdateEnd}
rfId={props.rfId}
/> />
); );
})} })}
+2
View File
@@ -84,6 +84,7 @@ const GraphView = ({
noWheelClassName, noWheelClassName,
noPanClassName, noPanClassName,
elevateEdgesOnSelect, elevateEdgesOnSelect,
id,
}: GraphViewProps) => { }: GraphViewProps) => {
useOnInitHandler(onInit); useOnInitHandler(onInit);
@@ -139,6 +140,7 @@ const GraphView = ({
defaultMarkerColor={defaultMarkerColor} defaultMarkerColor={defaultMarkerColor}
noPanClassName={noPanClassName} noPanClassName={noPanClassName}
elevateEdgesOnSelect={!!elevateEdgesOnSelect} elevateEdgesOnSelect={!!elevateEdgesOnSelect}
rfId={id}
/> />
<NodeRenderer <NodeRenderer
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
+1
View File
@@ -122,6 +122,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
isParent={!!node[internalsSymbol]?.isParent} isParent={!!node[internalsSymbol]?.isParent}
noDragClassName={props.noDragClassName} noDragClassName={props.noDragClassName}
noPanClassName={props.noPanClassName} noPanClassName={props.noPanClassName}
initialized={!!node.width && !!node.height}
/> />
); );
})} })}
+1
View File
@@ -211,6 +211,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
noWheelClassName={noWheelClassName} noWheelClassName={noWheelClassName}
noPanClassName={noPanClassName} noPanClassName={noPanClassName}
elevateEdgesOnSelect={elevateEdgesOnSelect} elevateEdgesOnSelect={elevateEdgesOnSelect}
id={rest?.id}
/> />
<StoreUpdater <StoreUpdater
nodes={nodes} nodes={nodes}
+8
View File
@@ -84,6 +84,14 @@ export function updatePosition(
} }
currentExtent = nodeExtent; currentExtent = nodeExtent;
} }
} else if (dragItem.extent && dragItem.parentNode) {
const parent = nodeInternals.get(dragItem.parentNode);
const parentX = parent?.positionAbsolute?.x ?? 0;
const parentY = parent?.positionAbsolute?.y ?? 0;
currentExtent = [
[dragItem.extent[0][0] + parentX, dragItem.extent[0][1] + parentY],
[dragItem.extent[1][0] + parentX, dragItem.extent[1][1] + parentY],
];
} }
dragItem.position = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition; dragItem.position = currentExtent ? clampPosition(nextPosition, currentExtent as CoordinateExtent) : nextPosition;
+2 -2
View File
@@ -69,8 +69,8 @@ function useVisibleEdges(onlyRenderVisible: boolean, nodeInternals: NodeInternal
targetNode?.width && targetNode?.width &&
targetNode?.height && targetNode?.height &&
isEdgeVisible({ isEdgeVisible({
sourcePos: sourceNode.position || { x: 0, y: 0 }, sourcePos: sourceNode.positionAbsolute || { x: 0, y: 0 },
targetPos: targetNode.position || { x: 0, y: 0 }, targetPos: targetNode.positionAbsolute || { x: 0, y: 0 },
sourceWidth: sourceNode.width, sourceWidth: sourceNode.width,
sourceHeight: sourceNode.height, sourceHeight: sourceNode.height,
targetWidth: targetNode.width, targetWidth: targetNode.width,
+1
View File
@@ -19,6 +19,7 @@ export {
export { default as SmoothStepEdge, getSmoothStepPath } from './components/Edges/SmoothStepEdge'; export { default as SmoothStepEdge, getSmoothStepPath } from './components/Edges/SmoothStepEdge';
export * from './additional-components'; export * from './additional-components';
export { internalsSymbol } from './utils';
export { export {
isNode, isNode,
isEdge, isEdge,
+1 -1
View File
@@ -105,7 +105,7 @@ export function fitView(get: GetState<ReactFlowState>, options: InternalFitViewO
if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) { if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) {
if (d3Zoom && d3Selection) { if (d3Zoom && d3Selection) {
const nodes = Array.from(nodeInternals.values()).filter((n) => const nodes = Array.from(nodeInternals.values()).filter((n) =>
options.includeHiddenNodes ? !n.parentNode && n.width && n.height : !n.parentNode && !n.hidden options.includeHiddenNodes ? n.width && n.height : !n.hidden
); );
const nodesInitialized = nodes.every((n) => n.width && n.height); const nodesInitialized = nodes.every((n) => n.width && n.height);
+1
View File
@@ -121,6 +121,7 @@ export interface WrapEdgeProps<T = any> {
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void; onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
markerStart?: EdgeMarkerType; markerStart?: EdgeMarkerType;
markerEnd?: EdgeMarkerType; markerEnd?: EdgeMarkerType;
rfId?: string;
} }
export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> { export interface EdgeSmoothStepProps<T = any> extends EdgeProps<T> {
+1 -2
View File
@@ -64,8 +64,7 @@ export interface WrapNodeProps<T = any> {
isConnectable: boolean; isConnectable: boolean;
xPos: number; xPos: number;
yPos: number; yPos: number;
width?: number | null; initialized: boolean;
height?: number | null;
isSelectable: boolean; isSelectable: boolean;
isDraggable: boolean; isDraggable: boolean;
selectNodesOnDrag: boolean; selectNodesOnDrag: boolean;
+5 -3
View File
@@ -30,7 +30,7 @@ export const getIncomers = <T = any, U extends T = T>(node: Node<U>, nodes: Node
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): string => const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection): string =>
`reactflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`; `reactflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
export const getMarkerId = (marker: EdgeMarkerType | undefined): string => { export const getMarkerId = (marker: EdgeMarkerType | undefined, rfId?: string): string => {
if (typeof marker === 'undefined') { if (typeof marker === 'undefined') {
return ''; return '';
} }
@@ -39,10 +39,12 @@ export const getMarkerId = (marker: EdgeMarkerType | undefined): string => {
return marker; return marker;
} }
return Object.keys(marker) const idPrefix = rfId ? `${rfId}__` : '';
return `${idPrefix}${Object.keys(marker)
.sort() .sort()
.map((key: string) => `${key}=${(marker as any)[key]}`) .map((key: string) => `${key}=${(marker as any)[key]}`)
.join('&'); .join('&')}`;
}; };
const connectionExists = (edge: Edge, edges: Edge[]) => { const connectionExists = (edge: Edge, edges: Edge[]) => {