refactor(edge-renderer): work with edge ids instead of edges, like node renderer
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
|||||||
addEdge,
|
addEdge,
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
ReactFlowInstance,
|
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
@@ -97,7 +96,6 @@ const initialEdges: Edge[] = [
|
|||||||
{ id: 'e5-6', source: '5', target: '6', label: 'This edge can be updated from both sides' },
|
{ id: 'e5-6', source: '5', target: '6', label: 'This edge can be updated from both sides' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
|
||||||
const onEdgeUpdateStart = (_: ReactMouseEvent, edge: Edge, handleType: HandleType) =>
|
const onEdgeUpdateStart = (_: ReactMouseEvent, edge: Edge, handleType: HandleType) =>
|
||||||
console.log(`start update ${handleType} handle`, edge);
|
console.log(`start update ${handleType} handle`, edge);
|
||||||
const onEdgeUpdateEnd = (_: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) =>
|
const onEdgeUpdateEnd = (_: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) =>
|
||||||
@@ -111,7 +109,6 @@ const UpdatableEdge = () => {
|
|||||||
const onConnect = (connection: Connection) => setEdges((els) => addEdge(connection, 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));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -125,12 +122,12 @@ const UpdatableEdge = () => {
|
|||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onInit={onInit}
|
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
onEdgeUpdate={onEdgeUpdate}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
|
fitView
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// Updatable edges have a anchors around their handles to update the edge.
|
||||||
|
import { XYHandle, type Connection, getEdgePosition } from '@xyflow/system';
|
||||||
|
|
||||||
|
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
||||||
|
import type { EdgeWrapperProps, Edge } from '../../types/edges';
|
||||||
|
import { useStoreApi } from '../../hooks/useStore';
|
||||||
|
|
||||||
|
type EdgeUpdateAnchorsProps = {
|
||||||
|
edgePosition: NonNullable<ReturnType<typeof getEdgePosition>>;
|
||||||
|
edge: Edge;
|
||||||
|
isUpdatable: boolean | 'source' | 'target';
|
||||||
|
edgeUpdaterRadius: EdgeWrapperProps['edgeUpdaterRadius'];
|
||||||
|
sourceHandleId: EdgeWrapperProps['sourceHandleId'];
|
||||||
|
targetHandleId: EdgeWrapperProps['targetHandleId'];
|
||||||
|
onEdgeUpdate: EdgeWrapperProps['onEdgeUpdate'];
|
||||||
|
onEdgeUpdateStart: EdgeWrapperProps['onEdgeUpdateStart'];
|
||||||
|
onEdgeUpdateEnd: EdgeWrapperProps['onEdgeUpdateEnd'];
|
||||||
|
setUpdateHover: (hover: boolean) => void;
|
||||||
|
setUpdating: (updating: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function EdgeUpdateAnchors({
|
||||||
|
isUpdatable,
|
||||||
|
edgeUpdaterRadius,
|
||||||
|
onEdgeUpdate,
|
||||||
|
onEdgeUpdateStart,
|
||||||
|
onEdgeUpdateEnd,
|
||||||
|
setUpdating,
|
||||||
|
setUpdateHover,
|
||||||
|
edgePosition,
|
||||||
|
edge,
|
||||||
|
targetHandleId,
|
||||||
|
sourceHandleId,
|
||||||
|
}: EdgeUpdateAnchorsProps) {
|
||||||
|
const store = useStoreApi();
|
||||||
|
|
||||||
|
const handleEdgeUpdater = (event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
||||||
|
// avoid triggering edge updater if mouse btn is not left
|
||||||
|
if (event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
autoPanOnConnect,
|
||||||
|
domNode,
|
||||||
|
isValidConnection,
|
||||||
|
connectionMode,
|
||||||
|
connectionRadius,
|
||||||
|
lib,
|
||||||
|
onConnectStart,
|
||||||
|
onConnectEnd,
|
||||||
|
cancelConnection,
|
||||||
|
nodes,
|
||||||
|
panBy,
|
||||||
|
updateConnection,
|
||||||
|
} = store.getState();
|
||||||
|
const nodeId = isSourceHandle ? edge.target : edge.source;
|
||||||
|
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||||
|
const handleType = isSourceHandle ? 'target' : 'source';
|
||||||
|
|
||||||
|
const isTarget = isSourceHandle;
|
||||||
|
|
||||||
|
setUpdating(true);
|
||||||
|
onEdgeUpdateStart?.(event, edge, handleType);
|
||||||
|
|
||||||
|
const _onEdgeUpdateEnd = (evt: MouseEvent | TouchEvent) => {
|
||||||
|
setUpdating(false);
|
||||||
|
onEdgeUpdateEnd?.(evt, edge, handleType);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection);
|
||||||
|
|
||||||
|
XYHandle.onPointerDown(event.nativeEvent, {
|
||||||
|
autoPanOnConnect,
|
||||||
|
connectionMode,
|
||||||
|
connectionRadius,
|
||||||
|
domNode,
|
||||||
|
handleId,
|
||||||
|
nodeId,
|
||||||
|
nodes,
|
||||||
|
isTarget,
|
||||||
|
edgeUpdaterType: handleType,
|
||||||
|
lib,
|
||||||
|
cancelConnection,
|
||||||
|
panBy,
|
||||||
|
isValidConnection,
|
||||||
|
onConnect: onConnectEdge,
|
||||||
|
onConnectStart,
|
||||||
|
onConnectEnd,
|
||||||
|
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
||||||
|
updateConnection,
|
||||||
|
getTransform: () => store.getState().transform,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||||
|
handleEdgeUpdater(event, true);
|
||||||
|
const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||||
|
handleEdgeUpdater(event, false);
|
||||||
|
const onEdgeUpdaterMouseEnter = () => setUpdateHover(true);
|
||||||
|
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(isUpdatable === 'source' || isUpdatable === true) && (
|
||||||
|
<EdgeAnchor
|
||||||
|
position={edgePosition.sourcePosition}
|
||||||
|
centerX={edgePosition.sourceX}
|
||||||
|
centerY={edgePosition.sourceY}
|
||||||
|
radius={edgeUpdaterRadius}
|
||||||
|
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
||||||
|
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||||
|
onMouseOut={onEdgeUpdaterMouseOut}
|
||||||
|
type="source"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(isUpdatable === 'target' || isUpdatable === true) && (
|
||||||
|
<EdgeAnchor
|
||||||
|
position={edgePosition.targetPosition}
|
||||||
|
centerX={edgePosition.targetX}
|
||||||
|
centerY={edgePosition.targetY}
|
||||||
|
radius={edgeUpdaterRadius}
|
||||||
|
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
||||||
|
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||||
|
onMouseOut={onEdgeUpdaterMouseOut}
|
||||||
|
type="target"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EdgeUpdateAnchors;
|
||||||
@@ -1,43 +1,21 @@
|
|||||||
import { memo, useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react';
|
import { memo, useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import {
|
import { getMarkerId, elementSelectionKeys, getEdgePosition, errorMessages, getEdgeZIndex } from '@xyflow/system';
|
||||||
getMarkerId,
|
|
||||||
elementSelectionKeys,
|
|
||||||
XYHandle,
|
|
||||||
type Connection,
|
|
||||||
getEdgePosition,
|
|
||||||
errorMessages,
|
|
||||||
getEdgeZIndex,
|
|
||||||
} from '@xyflow/system';
|
|
||||||
|
|
||||||
import { useStoreApi, useStore } from '../../hooks/useStore';
|
import { useStoreApi, useStore } from '../../hooks/useStore';
|
||||||
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
||||||
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
|
||||||
import { getMouseHandler } from '../Edges/utils';
|
|
||||||
import type { EdgeWrapperProps, Node } from '../../types';
|
import type { EdgeWrapperProps, Node } from '../../types';
|
||||||
import { builtinEdgeTypes } from './utils';
|
import { builtinEdgeTypes } from './utils';
|
||||||
|
import EdgeUpdateAnchors from './EdgeUpdateAnchors';
|
||||||
|
|
||||||
function EdgeWrapper({
|
function EdgeWrapper({
|
||||||
id,
|
id,
|
||||||
className,
|
edgesFocusable,
|
||||||
type,
|
edgesUpdatable,
|
||||||
data,
|
elementsSelectable,
|
||||||
onClick,
|
onClick,
|
||||||
onEdgeDoubleClick,
|
onDoubleClick,
|
||||||
selected,
|
|
||||||
animated,
|
|
||||||
label,
|
|
||||||
labelStyle,
|
|
||||||
labelShowBg,
|
|
||||||
labelBgStyle,
|
|
||||||
labelBgPadding,
|
|
||||||
labelBgBorderRadius,
|
|
||||||
style,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
isSelectable,
|
|
||||||
hidden,
|
|
||||||
sourceHandleId,
|
sourceHandleId,
|
||||||
targetHandleId,
|
targetHandleId,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
@@ -48,20 +26,15 @@ function EdgeWrapper({
|
|||||||
onEdgeUpdate,
|
onEdgeUpdate,
|
||||||
onEdgeUpdateStart,
|
onEdgeUpdateStart,
|
||||||
onEdgeUpdateEnd,
|
onEdgeUpdateEnd,
|
||||||
markerEnd,
|
|
||||||
markerStart,
|
|
||||||
rfId,
|
rfId,
|
||||||
ariaLabel,
|
|
||||||
isFocusable,
|
|
||||||
isUpdatable,
|
|
||||||
pathOptions,
|
|
||||||
interactionWidth,
|
|
||||||
edgeTypes,
|
edgeTypes,
|
||||||
zIndex: edgeZIndex,
|
|
||||||
elevateEdgesOnSelect,
|
elevateEdgesOnSelect,
|
||||||
|
noPanClassName,
|
||||||
onError,
|
onError,
|
||||||
}: EdgeWrapperProps): JSX.Element | null {
|
}: EdgeWrapperProps): JSX.Element | null {
|
||||||
let edgeType = type || 'default';
|
const edge = useStore((s) => s.edgeLookup.get(id)!);
|
||||||
|
|
||||||
|
let edgeType = edge.type || 'default';
|
||||||
let EdgeComponent = edgeTypes?.[edgeType] || builtinEdgeTypes[edgeType];
|
let EdgeComponent = edgeTypes?.[edgeType] || builtinEdgeTypes[edgeType];
|
||||||
|
|
||||||
if (EdgeComponent === undefined) {
|
if (EdgeComponent === undefined) {
|
||||||
@@ -70,23 +43,29 @@ function EdgeWrapper({
|
|||||||
EdgeComponent = builtinEdgeTypes.default;
|
EdgeComponent = builtinEdgeTypes.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
||||||
|
const isUpdatable =
|
||||||
|
typeof onEdgeUpdate !== 'undefined' &&
|
||||||
|
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
|
||||||
|
const isSelectable = !!(edge.selectable || (elementsSelectable && typeof edge.selectable === 'undefined'));
|
||||||
|
|
||||||
const edgeRef = useRef<SVGGElement>(null);
|
const edgeRef = useRef<SVGGElement>(null);
|
||||||
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
||||||
const [updating, setUpdating] = useState<boolean>(false);
|
const [updating, setUpdating] = useState<boolean>(false);
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const prevSourceNode = useRef<Node | undefined>();
|
const prevSourceNode = useRef<Node | undefined>();
|
||||||
const prevTargetNode = useRef<Node | undefined>();
|
const prevTargetNode = useRef<Node | undefined>();
|
||||||
const prevZIndex = useRef<number | undefined>(edgeZIndex);
|
const prevZIndex = useRef<number | undefined>(edge.zIndex);
|
||||||
const prevEdgePosition = useRef<ReturnType<typeof getEdgePosition> | null>(null);
|
const prevEdgePosition = useRef<ReturnType<typeof getEdgePosition> | null>(null);
|
||||||
|
|
||||||
const { edgePosition, zIndex } = useStore(
|
const { edgePosition, zIndex } = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(state) => {
|
(state) => {
|
||||||
const sourceNode = state.nodeLookup.get(source);
|
const sourceNode = state.nodeLookup.get(edge.source);
|
||||||
const targetNode = state.nodeLookup.get(target);
|
const targetNode = state.nodeLookup.get(edge.target);
|
||||||
|
|
||||||
if (!sourceNode || !targetNode) {
|
if (!sourceNode || !targetNode) {
|
||||||
return { edgePosition: null, zIndex: edgeZIndex };
|
return { edgePosition: null, zIndex: edge.zIndex };
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodesChanged = prevSourceNode.current !== sourceNode || prevTargetNode.current !== targetNode;
|
const nodesChanged = prevSourceNode.current !== sourceNode || prevTargetNode.current !== targetNode;
|
||||||
@@ -105,28 +84,33 @@ function EdgeWrapper({
|
|||||||
onError: state.onError,
|
onError: state.onError,
|
||||||
})
|
})
|
||||||
: prevEdgePosition.current;
|
: prevEdgePosition.current;
|
||||||
prevZIndex.current = getEdgeZIndex(selected, edgeZIndex, sourceNode, targetNode, elevateEdgesOnSelect);
|
prevZIndex.current = getEdgeZIndex(edge.selected, edge.zIndex, sourceNode, targetNode, elevateEdgesOnSelect);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
edgePosition: prevEdgePosition.current,
|
edgePosition: prevEdgePosition.current,
|
||||||
zIndex: prevZIndex.current,
|
zIndex: prevZIndex.current,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[source, target, selected, edgeZIndex]
|
[edge.source, edge.target, edge.selected, edge.zIndex]
|
||||||
),
|
),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|
||||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
const markerStartUrl = useMemo(
|
||||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined),
|
||||||
|
[edge.markerStart, rfId]
|
||||||
|
);
|
||||||
|
const markerEndUrl = useMemo(
|
||||||
|
() => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined),
|
||||||
|
[edge.markerEnd, rfId]
|
||||||
|
);
|
||||||
|
|
||||||
if (hidden || !edgePosition) {
|
if (edge.hidden || !edgePosition) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onEdgeClick = (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
const onEdgeClick = (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||||
const { edges, addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState();
|
const { addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState();
|
||||||
const edge = edges.find((e) => e.id === id);
|
|
||||||
|
|
||||||
if (!edge) {
|
if (!edge) {
|
||||||
return;
|
return;
|
||||||
@@ -148,82 +132,31 @@ function EdgeWrapper({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEdgeDoubleClickHandler = getMouseHandler(id, store.getState, onEdgeDoubleClick);
|
const onEdgeDoubleClick = onDoubleClick
|
||||||
const onEdgeContextMenu = getMouseHandler(id, store.getState, onContextMenu);
|
? (event: React.MouseEvent) => {
|
||||||
const onEdgeMouseEnter = getMouseHandler(id, store.getState, onMouseEnter);
|
onDoubleClick(event, { ...edge });
|
||||||
const onEdgeMouseMove = getMouseHandler(id, store.getState, onMouseMove);
|
}
|
||||||
const onEdgeMouseLeave = getMouseHandler(id, store.getState, onMouseLeave);
|
: undefined;
|
||||||
|
const onEdgeContextMenu = onContextMenu
|
||||||
const handleEdgeUpdater = (event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
? (event: React.MouseEvent) => {
|
||||||
// avoid triggering edge updater if mouse btn is not left
|
onContextMenu(event, { ...edge });
|
||||||
if (event.button !== 0) {
|
}
|
||||||
return;
|
: undefined;
|
||||||
}
|
const onEdgeMouseEnter = onMouseEnter
|
||||||
|
? (event: React.MouseEvent) => {
|
||||||
const {
|
onMouseEnter(event, { ...edge });
|
||||||
autoPanOnConnect,
|
}
|
||||||
domNode,
|
: undefined;
|
||||||
edges,
|
const onEdgeMouseMove = onMouseMove
|
||||||
isValidConnection,
|
? (event: React.MouseEvent) => {
|
||||||
connectionMode,
|
onMouseMove(event, { ...edge });
|
||||||
connectionRadius,
|
}
|
||||||
lib,
|
: undefined;
|
||||||
onConnectStart,
|
const onEdgeMouseLeave = onMouseLeave
|
||||||
onConnectEnd,
|
? (event: React.MouseEvent) => {
|
||||||
cancelConnection,
|
onMouseLeave(event, { ...edge });
|
||||||
nodes,
|
}
|
||||||
panBy,
|
: undefined;
|
||||||
updateConnection,
|
|
||||||
} = store.getState();
|
|
||||||
const nodeId = isSourceHandle ? target : source;
|
|
||||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
|
||||||
const handleType = isSourceHandle ? 'target' : 'source';
|
|
||||||
|
|
||||||
const isTarget = isSourceHandle;
|
|
||||||
const edge = edges.find((e) => e.id === id)!;
|
|
||||||
|
|
||||||
setUpdating(true);
|
|
||||||
onEdgeUpdateStart?.(event, edge, handleType);
|
|
||||||
|
|
||||||
const _onEdgeUpdateEnd = (evt: MouseEvent | TouchEvent) => {
|
|
||||||
setUpdating(false);
|
|
||||||
onEdgeUpdateEnd?.(evt, edge, handleType);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection);
|
|
||||||
|
|
||||||
XYHandle.onPointerDown(event.nativeEvent, {
|
|
||||||
autoPanOnConnect,
|
|
||||||
connectionMode,
|
|
||||||
connectionRadius,
|
|
||||||
domNode,
|
|
||||||
handleId,
|
|
||||||
nodeId,
|
|
||||||
nodes,
|
|
||||||
isTarget,
|
|
||||||
edgeUpdaterType: handleType,
|
|
||||||
lib,
|
|
||||||
cancelConnection,
|
|
||||||
panBy,
|
|
||||||
isValidConnection,
|
|
||||||
onConnect: onConnectEdge,
|
|
||||||
onConnectStart,
|
|
||||||
onConnectEnd,
|
|
||||||
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
|
||||||
updateConnection,
|
|
||||||
getTransform: () => store.getState().transform,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
|
||||||
handleEdgeUpdater(event, true);
|
|
||||||
const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
|
||||||
handleEdgeUpdater(event, false);
|
|
||||||
|
|
||||||
const onEdgeUpdaterMouseEnter = () => setUpdateHover(true);
|
|
||||||
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
|
||||||
|
|
||||||
const inactive = !isSelectable && !onClick;
|
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
||||||
@@ -244,12 +177,18 @@ function EdgeWrapper({
|
|||||||
<g
|
<g
|
||||||
className={cc([
|
className={cc([
|
||||||
'react-flow__edge',
|
'react-flow__edge',
|
||||||
`react-flow__edge-${type}`,
|
`react-flow__edge-${edgeType}`,
|
||||||
className,
|
edge.className,
|
||||||
{ selected, animated, inactive, updating: updateHover },
|
noPanClassName,
|
||||||
|
{
|
||||||
|
selected: edge.selected,
|
||||||
|
animated: edge.animated,
|
||||||
|
inactive: !isSelectable && !onClick,
|
||||||
|
updating: updateHover,
|
||||||
|
},
|
||||||
])}
|
])}
|
||||||
onClick={onEdgeClick}
|
onClick={onEdgeClick}
|
||||||
onDoubleClick={onEdgeDoubleClickHandler}
|
onDoubleClick={onEdgeDoubleClick}
|
||||||
onContextMenu={onEdgeContextMenu}
|
onContextMenu={onEdgeContextMenu}
|
||||||
onMouseEnter={onEdgeMouseEnter}
|
onMouseEnter={onEdgeMouseEnter}
|
||||||
onMouseMove={onEdgeMouseMove}
|
onMouseMove={onEdgeMouseMove}
|
||||||
@@ -259,25 +198,27 @@ function EdgeWrapper({
|
|||||||
role={isFocusable ? 'button' : 'img'}
|
role={isFocusable ? 'button' : 'img'}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-testid={`rf__edge-${id}`}
|
data-testid={`rf__edge-${id}`}
|
||||||
aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`}
|
aria-label={
|
||||||
|
edge.ariaLabel === null ? undefined : edge.ariaLabel || `Edge from ${edge.source} to ${edge.target}`
|
||||||
|
}
|
||||||
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
|
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
|
||||||
ref={edgeRef}
|
ref={edgeRef}
|
||||||
>
|
>
|
||||||
{!updating && (
|
{!updating && (
|
||||||
<EdgeComponent
|
<EdgeComponent
|
||||||
id={id}
|
id={id}
|
||||||
source={source}
|
source={edge.source}
|
||||||
target={target}
|
target={edge.target}
|
||||||
selected={selected}
|
selected={edge.selected}
|
||||||
animated={animated}
|
animated={edge.animated}
|
||||||
label={label}
|
label={edge.label}
|
||||||
labelStyle={labelStyle}
|
labelStyle={edge.labelStyle}
|
||||||
labelShowBg={labelShowBg}
|
labelShowBg={edge.labelShowBg}
|
||||||
labelBgStyle={labelBgStyle}
|
labelBgStyle={edge.labelBgStyle}
|
||||||
labelBgPadding={labelBgPadding}
|
labelBgPadding={edge.labelBgPadding}
|
||||||
labelBgBorderRadius={labelBgBorderRadius}
|
labelBgBorderRadius={edge.labelBgBorderRadius}
|
||||||
data={data}
|
data={edge.data}
|
||||||
style={style}
|
style={edge.style}
|
||||||
sourceX={edgePosition.sourceX}
|
sourceX={edgePosition.sourceX}
|
||||||
sourceY={edgePosition.sourceY}
|
sourceY={edgePosition.sourceY}
|
||||||
targetX={edgePosition.targetX}
|
targetX={edgePosition.targetX}
|
||||||
@@ -288,37 +229,24 @@ function EdgeWrapper({
|
|||||||
targetHandleId={targetHandleId}
|
targetHandleId={targetHandleId}
|
||||||
markerStart={markerStartUrl}
|
markerStart={markerStartUrl}
|
||||||
markerEnd={markerEndUrl}
|
markerEnd={markerEndUrl}
|
||||||
pathOptions={pathOptions}
|
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
|
||||||
interactionWidth={interactionWidth}
|
interactionWidth={edge.interactionWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isUpdatable && (
|
{isUpdatable && (
|
||||||
<>
|
<EdgeUpdateAnchors
|
||||||
{(isUpdatable === 'source' || isUpdatable === true) && (
|
edge={edge}
|
||||||
<EdgeAnchor
|
isUpdatable={isUpdatable}
|
||||||
position={edgePosition.sourcePosition}
|
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||||
centerX={edgePosition.sourceX}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
centerY={edgePosition.sourceY}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
radius={edgeUpdaterRadius}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
edgePosition={edgePosition}
|
||||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
setUpdateHover={setUpdateHover}
|
||||||
onMouseOut={onEdgeUpdaterMouseOut}
|
setUpdating={setUpdating}
|
||||||
type="source"
|
sourceHandleId={sourceHandleId}
|
||||||
/>
|
targetHandleId={targetHandleId}
|
||||||
)}
|
/>
|
||||||
{(isUpdatable === 'target' || isUpdatable === true) && (
|
|
||||||
<EdgeAnchor
|
|
||||||
position={edgePosition.targetPosition}
|
|
||||||
centerX={edgePosition.targetX}
|
|
||||||
centerY={edgePosition.targetY}
|
|
||||||
radius={edgeUpdaterRadius}
|
|
||||||
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
|
||||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
|
||||||
onMouseOut={onEdgeUpdaterMouseOut}
|
|
||||||
type="target"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
|
||||||
import type { StoreApi } from 'zustand';
|
|
||||||
|
|
||||||
import type { Edge, ReactFlowState } from '../../types';
|
|
||||||
|
|
||||||
export function getMouseHandler(
|
|
||||||
id: string,
|
|
||||||
getState: StoreApi<ReactFlowState>['getState'],
|
|
||||||
handler?: (event: ReactMouseEvent<SVGGElement, MouseEvent>, edge: Edge) => void
|
|
||||||
) {
|
|
||||||
return handler === undefined
|
|
||||||
? handler
|
|
||||||
: (event: ReactMouseEvent<SVGGElement, MouseEvent>) => {
|
|
||||||
const edge = getState().edges.find((e) => e.id === id);
|
|
||||||
|
|
||||||
if (edge) {
|
|
||||||
handler(event, { ...edge });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { memo, ReactNode } from 'react';
|
import { memo, ReactNode } from 'react';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import cc from 'classcat';
|
|
||||||
|
|
||||||
import { useStore } from '../../hooks/useStore';
|
import { useStore } from '../../hooks/useStore';
|
||||||
import useVisibleEdges from '../../hooks/useVisibleEdges';
|
import useVisibleEdgeIds from '../../hooks/useVisibleEdges';
|
||||||
import MarkerDefinitions from './MarkerDefinitions';
|
import MarkerDefinitions from './MarkerDefinitions';
|
||||||
import { GraphViewProps } from '../GraphView';
|
import { GraphViewProps } from '../GraphView';
|
||||||
import type { ReactFlowState } from '../../types';
|
import type { ReactFlowState } from '../../types';
|
||||||
@@ -63,7 +62,7 @@ const EdgeRenderer = ({
|
|||||||
children,
|
children,
|
||||||
}: EdgeRendererProps) => {
|
}: EdgeRendererProps) => {
|
||||||
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
|
const { edgesFocusable, edgesUpdatable, elementsSelectable, onError } = useStore(selector, shallow);
|
||||||
const edges = useVisibleEdges(onlyRenderVisibleElements);
|
const edgeIds = useVisibleEdgeIds(onlyRenderVisibleElements);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="react-flow__edges">
|
<div className="react-flow__edges">
|
||||||
@@ -71,38 +70,15 @@ const EdgeRenderer = ({
|
|||||||
<MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />
|
<MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{edges.map((edge) => {
|
{edgeIds.map((id) => {
|
||||||
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
|
||||||
const isUpdatable =
|
|
||||||
typeof onEdgeUpdate !== 'undefined' &&
|
|
||||||
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
|
|
||||||
const isSelectable = !!(edge.selectable || (elementsSelectable && typeof edge.selectable === 'undefined'));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EdgeWrapper
|
<EdgeWrapper
|
||||||
key={edge.id}
|
key={id}
|
||||||
id={edge.id}
|
id={id}
|
||||||
className={cc([edge.className, noPanClassName])}
|
edgesFocusable={edgesFocusable}
|
||||||
type={edge.type}
|
edgesUpdatable={edgesUpdatable}
|
||||||
data={edge.data}
|
elementsSelectable={elementsSelectable}
|
||||||
selected={!!edge.selected}
|
noPanClassName={noPanClassName}
|
||||||
animated={!!edge.animated}
|
|
||||||
hidden={!!edge.hidden}
|
|
||||||
zIndex={edge.zIndex}
|
|
||||||
label={edge.label}
|
|
||||||
labelStyle={edge.labelStyle}
|
|
||||||
labelShowBg={edge.labelShowBg}
|
|
||||||
labelBgStyle={edge.labelBgStyle}
|
|
||||||
labelBgPadding={edge.labelBgPadding}
|
|
||||||
labelBgBorderRadius={edge.labelBgBorderRadius}
|
|
||||||
style={edge.style}
|
|
||||||
source={edge.source}
|
|
||||||
target={edge.target}
|
|
||||||
sourceHandleId={edge.sourceHandle}
|
|
||||||
targetHandleId={edge.targetHandle}
|
|
||||||
markerEnd={edge.markerEnd}
|
|
||||||
markerStart={edge.markerStart}
|
|
||||||
isSelectable={isSelectable}
|
|
||||||
onEdgeUpdate={onEdgeUpdate}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
onContextMenu={onEdgeContextMenu}
|
onContextMenu={onEdgeContextMenu}
|
||||||
onMouseEnter={onEdgeMouseEnter}
|
onMouseEnter={onEdgeMouseEnter}
|
||||||
@@ -110,15 +86,10 @@ const EdgeRenderer = ({
|
|||||||
onMouseLeave={onEdgeMouseLeave}
|
onMouseLeave={onEdgeMouseLeave}
|
||||||
onClick={onEdgeClick}
|
onClick={onEdgeClick}
|
||||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
onDoubleClick={onEdgeDoubleClick}
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
rfId={rfId}
|
rfId={rfId}
|
||||||
ariaLabel={edge.ariaLabel}
|
|
||||||
isFocusable={isFocusable}
|
|
||||||
isUpdatable={isUpdatable}
|
|
||||||
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
|
|
||||||
interactionWidth={edge.interactionWidth}
|
|
||||||
onError={onError}
|
onError={onError}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
elevateEdgesOnSelect={elevateEdgesOnSelect}
|
elevateEdgesOnSelect={elevateEdgesOnSelect}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { shallow } from 'zustand/shallow';
|
|||||||
import { isEdgeVisible } from '@xyflow/system';
|
import { isEdgeVisible } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '../hooks/useStore';
|
import { useStore } from '../hooks/useStore';
|
||||||
import { type Edge, type ReactFlowState } from '../types';
|
import { type ReactFlowState } from '../types';
|
||||||
|
|
||||||
function useVisibleEdges(onlyRenderVisible: boolean): Edge[] {
|
function useVisibleEdgeIds(onlyRenderVisible: boolean): string[] {
|
||||||
const edges = useStore(
|
const edgeIds = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(s: ReactFlowState) => {
|
(s: ReactFlowState) => {
|
||||||
const visibleEdges =
|
const visibleEdges =
|
||||||
@@ -29,14 +29,14 @@ function useVisibleEdges(onlyRenderVisible: boolean): Edge[] {
|
|||||||
})
|
})
|
||||||
: s.edges;
|
: s.edges;
|
||||||
|
|
||||||
return visibleEdges;
|
return visibleEdges.map((edge) => edge.id);
|
||||||
},
|
},
|
||||||
[onlyRenderVisible]
|
[onlyRenderVisible]
|
||||||
),
|
),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|
||||||
return edges;
|
return edgeIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useVisibleEdges;
|
export default useVisibleEdgeIds;
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export {
|
|||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type ColorMode,
|
type ColorMode,
|
||||||
type ColorModeClass,
|
type ColorModeClass,
|
||||||
|
type HandleType,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
// system utils
|
// system utils
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ const createRFStore = ({
|
|||||||
set({ nodes: nodesWithInternalData });
|
set({ nodes: nodesWithInternalData });
|
||||||
},
|
},
|
||||||
setEdges: (edges: Edge[]) => {
|
setEdges: (edges: Edge[]) => {
|
||||||
const { defaultEdgeOptions = {}, connectionLookup } = get();
|
const { defaultEdgeOptions = {}, connectionLookup, edgeLookup } = get();
|
||||||
const nextEdges = edges.map((e) => ({ ...defaultEdgeOptions, ...e }));
|
const nextEdges = edges.map((e) => ({ ...defaultEdgeOptions, ...e }));
|
||||||
|
|
||||||
updateConnectionLookup(connectionLookup, nextEdges);
|
updateConnectionLookup(connectionLookup, edgeLookup, nextEdges);
|
||||||
|
|
||||||
set({ edges: nextEdges });
|
set({ edges: nextEdges });
|
||||||
},
|
},
|
||||||
@@ -79,13 +79,16 @@ const createRFStore = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (hasDefaultNodes) {
|
if (hasDefaultNodes) {
|
||||||
const { nodeLookup } = get();
|
const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||||
nextState.nodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
nextState.nodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
||||||
nodeOrigin: get().nodeOrigin,
|
nodeOrigin,
|
||||||
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (hasDefaultEdges) {
|
if (hasDefaultEdges) {
|
||||||
|
const { connectionLookup, edgeLookup } = get();
|
||||||
|
updateConnectionLookup(connectionLookup, edgeLookup, edges);
|
||||||
|
|
||||||
nextState.edges = edges;
|
nextState.edges = edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ const getInitialState = ({
|
|||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
} = {}): ReactFlowStore => {
|
} = {}): ReactFlowStore => {
|
||||||
const nodeLookup = new Map();
|
const nodeLookup = new Map();
|
||||||
const connectionLookup = updateConnectionLookup(new Map(), edges);
|
const connectionLookup = new Map();
|
||||||
|
const edgeLookup = new Map();
|
||||||
|
|
||||||
|
updateConnectionLookup(connectionLookup, edgeLookup, edges);
|
||||||
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false,
|
elevateNodesOnSelect: false,
|
||||||
@@ -47,6 +50,7 @@ const getInitialState = ({
|
|||||||
nodes: nextNodes,
|
nodes: nextNodes,
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
edges: edges,
|
edges: edges,
|
||||||
|
edgeLookup,
|
||||||
connectionLookup,
|
connectionLookup,
|
||||||
onNodesChange: null,
|
onNodesChange: null,
|
||||||
onEdgesChange: null,
|
onEdgesChange: null,
|
||||||
|
|||||||
@@ -58,9 +58,14 @@ export type Edge<T = any> = DefaultEdge<T> | SmoothStepEdgeType<T> | BezierEdgeT
|
|||||||
|
|
||||||
export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void;
|
export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void;
|
||||||
|
|
||||||
export type EdgeWrapperProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle'> & {
|
export type EdgeWrapperProps = {
|
||||||
|
id: string;
|
||||||
|
edgesFocusable: boolean;
|
||||||
|
edgesUpdatable: boolean;
|
||||||
|
elementsSelectable: boolean;
|
||||||
|
noPanClassName: string;
|
||||||
onClick?: EdgeMouseHandler;
|
onClick?: EdgeMouseHandler;
|
||||||
onEdgeDoubleClick?: EdgeMouseHandler;
|
onDoubleClick?: EdgeMouseHandler;
|
||||||
sourceHandleId?: string | null;
|
sourceHandleId?: string | null;
|
||||||
targetHandleId?: string | null;
|
targetHandleId?: string | null;
|
||||||
onEdgeUpdate?: OnEdgeUpdateFunc;
|
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||||
@@ -72,10 +77,6 @@ export type EdgeWrapperProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHa
|
|||||||
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void;
|
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void;
|
||||||
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
|
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
|
||||||
rfId?: string;
|
rfId?: string;
|
||||||
isFocusable: boolean;
|
|
||||||
isUpdatable: EdgeUpdatable;
|
|
||||||
isSelectable: boolean;
|
|
||||||
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
|
|
||||||
edgeTypes?: EdgeTypes;
|
edgeTypes?: EdgeTypes;
|
||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
elevateEdgesOnSelect?: boolean;
|
elevateEdgesOnSelect?: boolean;
|
||||||
@@ -94,13 +95,14 @@ export type EdgeProps<T = any> = Pick<
|
|||||||
Edge<T>,
|
Edge<T>,
|
||||||
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'
|
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'
|
||||||
> &
|
> &
|
||||||
Pick<EdgeWrapperProps, 'sourceHandleId' | 'targetHandleId' | 'interactionWidth'> &
|
Pick<EdgeWrapperProps, 'sourceHandleId' | 'targetHandleId'> &
|
||||||
EdgePosition &
|
EdgePosition &
|
||||||
EdgeLabelOptions & {
|
EdgeLabelOptions & {
|
||||||
markerStart?: string;
|
markerStart?: string;
|
||||||
markerEnd?: string;
|
markerEnd?: string;
|
||||||
// @TODO: how can we get better types for pathOptions?
|
// @TODO: how can we get better types for pathOptions?
|
||||||
pathOptions?: any;
|
pathOptions?: any;
|
||||||
|
interactionWidth?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerStart' | 'markerEnd' | 'interactionWidth'> &
|
export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerStart' | 'markerEnd' | 'interactionWidth'> &
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import {
|
|||||||
type OnMoveEnd,
|
type OnMoveEnd,
|
||||||
type IsValidConnection,
|
type IsValidConnection,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
Connection,
|
EdgeLookup,
|
||||||
|
ConnectionLookup,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -50,7 +51,8 @@ export type ReactFlowStore = {
|
|||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
nodeLookup: Map<string, Node>;
|
nodeLookup: Map<string, Node>;
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
connectionLookup: Map<string, Map<string, Connection>>;
|
edgeLookup: EdgeLookup<Edge>;
|
||||||
|
connectionLookup: ConnectionLookup;
|
||||||
|
|
||||||
onNodesChange: OnNodesChange | null;
|
onNodesChange: OnNodesChange | null;
|
||||||
onEdgesChange: OnEdgesChange | null;
|
onEdgesChange: OnEdgesChange | null;
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ export const getInitialStore = ({
|
|||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false
|
elevateNodesOnSelect: false
|
||||||
});
|
});
|
||||||
const connectionLookup = updateConnectionLookup(new Map(), edges);
|
const connectionLookup = new Map();
|
||||||
|
const edgeLookup = new Map();
|
||||||
|
updateConnectionLookup(connectionLookup, edgeLookup, edges);
|
||||||
|
|
||||||
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||||
|
|
||||||
@@ -96,7 +98,7 @@ export const getInitialStore = ({
|
|||||||
nodes: createNodesStore(nextNodes, nodeLookup),
|
nodes: createNodesStore(nextNodes, nodeLookup),
|
||||||
nodeLookup: readable<Map<string, Node>>(nodeLookup),
|
nodeLookup: readable<Map<string, Node>>(nodeLookup),
|
||||||
visibleNodes: readable<Node[]>([]),
|
visibleNodes: readable<Node[]>([]),
|
||||||
edges: createEdgesStore(edges, connectionLookup),
|
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
||||||
visibleEdges: readable<EdgeLayouted[]>([]),
|
visibleEdges: readable<EdgeLayouted[]>([]),
|
||||||
connectionLookup: readable<ConnectionLookup>(connectionLookup),
|
connectionLookup: readable<ConnectionLookup>(connectionLookup),
|
||||||
height: writable<number>(500),
|
height: writable<number>(500),
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
updateConnectionLookup,
|
updateConnectionLookup,
|
||||||
type Viewport,
|
type Viewport,
|
||||||
type PanZoomInstance,
|
type PanZoomInstance,
|
||||||
type ConnectionLookup
|
type ConnectionLookup,
|
||||||
|
type EdgeLookup
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||||
@@ -175,6 +176,7 @@ export const createNodesStore = (
|
|||||||
export const createEdgesStore = (
|
export const createEdgesStore = (
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
connectionLookup: ConnectionLookup,
|
connectionLookup: ConnectionLookup,
|
||||||
|
edgeLookup: EdgeLookup,
|
||||||
defaultOptions?: DefaultEdgeOptions
|
defaultOptions?: DefaultEdgeOptions
|
||||||
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
|
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
|
||||||
const { subscribe, set, update } = writable<Edge[]>([]);
|
const { subscribe, set, update } = writable<Edge[]>([]);
|
||||||
@@ -184,7 +186,7 @@ export const createEdgesStore = (
|
|||||||
const _set: typeof set = (eds: Edge[]) => {
|
const _set: typeof set = (eds: Edge[]) => {
|
||||||
const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds;
|
const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds;
|
||||||
|
|
||||||
updateConnectionLookup(connectionLookup, nextEdges);
|
updateConnectionLookup(connectionLookup, edgeLookup, nextEdges);
|
||||||
|
|
||||||
value = nextEdges;
|
value = nextEdges;
|
||||||
set(value);
|
set(value);
|
||||||
|
|||||||
@@ -77,3 +77,5 @@ export type EdgePosition = {
|
|||||||
sourcePosition: Position;
|
sourcePosition: Position;
|
||||||
targetPosition: Position;
|
targetPosition: Position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EdgeLookup = Map<string, EdgeBase>;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
XYZPosition,
|
XYZPosition,
|
||||||
ConnectionLookup,
|
ConnectionLookup,
|
||||||
EdgeBase,
|
EdgeBase,
|
||||||
|
EdgeLookup,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getDimensions, getHandleBounds } from './dom';
|
import { getDimensions, getHandleBounds } from './dom';
|
||||||
import { isNumeric } from './general';
|
import { isNumeric } from './general';
|
||||||
@@ -244,22 +245,25 @@ export function panBy({
|
|||||||
return transformChanged;
|
return transformChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateConnectionLookup(lookup: ConnectionLookup, edges: EdgeBase[]) {
|
export function updateConnectionLookup(lookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) {
|
||||||
lookup.clear();
|
lookup.clear();
|
||||||
|
edgeLookup.clear();
|
||||||
|
|
||||||
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
|
for (const edge of edges) {
|
||||||
if (source && target) {
|
edgeLookup.set(edge.id, edge);
|
||||||
const sourceKey = `${source}-source-${sourceHandle}`;
|
|
||||||
const targetKey = `${target}-target-${targetHandle}`;
|
|
||||||
|
|
||||||
const prevSource = lookup.get(sourceKey) || new Map();
|
const { source, target, sourceHandle = null, targetHandle = null } = edge;
|
||||||
const prevTarget = lookup.get(targetKey) || new Map();
|
|
||||||
const connection = { source, target, sourceHandle, targetHandle };
|
|
||||||
|
|
||||||
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
const sourceKey = `${source}-source-${sourceHandle}`;
|
||||||
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
const targetKey = `${target}-target-${targetHandle}`;
|
||||||
}
|
|
||||||
});
|
const prevSource = lookup.get(sourceKey) || new Map();
|
||||||
|
const prevTarget = lookup.get(targetKey) || new Map();
|
||||||
|
const connection = { source, target, sourceHandle, targetHandle };
|
||||||
|
|
||||||
|
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
||||||
|
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
||||||
|
}
|
||||||
|
|
||||||
return lookup;
|
return lookup;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user