diff --git a/packages/demo-app-ts/src/demos/stylesDemo/Styles.tsx b/packages/demo-app-ts/src/demos/stylesDemo/Styles.tsx
index e5b7f9d1..3c3f70e7 100644
--- a/packages/demo-app-ts/src/demos/stylesDemo/Styles.tsx
+++ b/packages/demo-app-ts/src/demos/stylesDemo/Styles.tsx
@@ -666,6 +666,47 @@ export const EdgeStyles = withTopologySetup(() => {
return null;
});
+export const FreezedEdge = withTopologySetup(() => {
+ useComponentFactory(defaultComponentFactory);
+ useComponentFactory(stylesComponentFactory);
+ const nodes: NodeModel[] = createGroupNodes('edges-group');
+ const edges: EdgeModel[] = [];
+
+ const middleNodeIndex = nodes.length - 1;
+ nodes.forEach((item, index) => {
+ if (index === middleNodeIndex) {
+ return;
+ }
+ const endIndex = index < nodes.length - 2 ? index + 1 : 0;
+ edges.push({
+ id: `edge-${index}-${endIndex}`,
+ type: 'edge',
+ source: nodes[index].id,
+ target: nodes[endIndex].id,
+ edgeStyle: EDGE_STYLES[index % EDGE_STYLE_COUNT],
+ data: { freezeEdgeDuringNodeDrag: true }
+ });
+ edges.push({
+ id: `edge-${middleNodeIndex}-${index}`,
+ type: 'edge',
+ source: nodes[middleNodeIndex].id,
+ target: nodes[index].id,
+ edgeStyle: EdgeStyle.default,
+ data: { freezeEdgeDuringNodeDrag: true }
+ });
+ });
+
+ useModel({
+ graph: {
+ id: 'g1',
+ type: 'graph'
+ },
+ nodes,
+ edges
+ });
+ return null;
+});
+
export const EdgeAnimationStyles = withTopologySetup(() => {
useComponentFactory(defaultComponentFactory);
useComponentFactory(stylesComponentFactory);
@@ -977,16 +1018,19 @@ export const StyleEdges: React.FunctionComponent = () => {
Edge Styles}>
- Animated Edges}>
+ Freeze Edge When Dragging Node}>
+
+
+ Animated Edges}>
- Edge Terminal Types}>
+ Edge Terminal Types}>
- Edge Terminal Status}>
+ Edge Terminal Status}>
- Edge Terminal Tags}>
+ Edge Terminal Tags}>
diff --git a/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx b/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx
index 7cce6ce5..5cc5ef8e 100644
--- a/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx
+++ b/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx
@@ -23,7 +23,8 @@ import {
groupDropTargetSpec,
graphDropTargetSpec,
NODE_DRAG_TYPE,
- CREATE_CONNECTOR_DROP_TYPE
+ CREATE_CONNECTOR_DROP_TYPE,
+ edgeDropTargetSpec
} from '@patternfly/react-topology';
import StyleNode from './StyleNode';
import StyleGroup from './StyleGroup';
@@ -132,7 +133,11 @@ const stylesComponentFactory: ComponentFactory = (
collect: (monitor) => ({
dragging: monitor.isDragging()
})
- })(withContextMenu(() => defaultMenu)(withSelection()(StyleEdge)))
+ })(
+ withDndDrop(
+ edgeDropTargetSpec
+ )(withContextMenu(() => defaultMenu)(withSelection()(StyleEdge)))
+ )
);
default:
return undefined;
diff --git a/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoContext.tsx b/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoContext.tsx
index f92fe388..7badd985 100644
--- a/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoContext.tsx
+++ b/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoContext.tsx
@@ -22,7 +22,8 @@ export class DemoModel {
showStatus: false,
showAnimations: false,
showTags: false,
- terminalTypes: false
+ terminalTypes: false,
+ freezeEdgeDuringNodeDrag: false
};
protected creationCountsP: { numNodes: number; numEdges: number; numGroups: number; nestedLevel: number } = {
numNodes: 6,
diff --git a/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoEdge.tsx b/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoEdge.tsx
index 5ca3a46d..eeda41a9 100644
--- a/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoEdge.tsx
+++ b/packages/demo-app-ts/src/demos/topologyPackageDemo/DemoEdge.tsx
@@ -37,6 +37,7 @@ const DemoEdge: React.FunctionComponent = ({ element, ...rest })
endTerminalStatus={options.showStatus && NODE_STATUSES[data.index % NODE_STATUSES.length]}
tag={options.showTags ? data.tag : undefined}
tagStatus={options.showStatus && NODE_STATUSES[data.index % NODE_STATUSES.length]}
+ freezeEdgeDuringNodeDrag={options.freezeEdgeDuringNodeDrag}
/>
);
};
diff --git a/packages/demo-app-ts/src/demos/topologyPackageDemo/OptionsContextBar.tsx b/packages/demo-app-ts/src/demos/topologyPackageDemo/OptionsContextBar.tsx
index 7081bb91..57d436ce 100644
--- a/packages/demo-app-ts/src/demos/topologyPackageDemo/OptionsContextBar.tsx
+++ b/packages/demo-app-ts/src/demos/topologyPackageDemo/OptionsContextBar.tsx
@@ -202,6 +202,19 @@ const OptionsContextBar: React.FC = observer(() => {
>
Tags
+
+ options.setEdgeOptions({
+ ...options.edgeOptions,
+ freezeEdgeDuringNodeDrag: !options.edgeOptions.freezeEdgeDuringNodeDrag
+ })
+ }
+ >
+ Freeze Edge During Node Drag
+
);
const edgeOptionsToggle = (toggleRef: React.Ref) => (
diff --git a/packages/demo-app-ts/src/demos/topologyPackageDemo/generator.ts b/packages/demo-app-ts/src/demos/topologyPackageDemo/generator.ts
index 2eed6fb5..880462d4 100644
--- a/packages/demo-app-ts/src/demos/topologyPackageDemo/generator.ts
+++ b/packages/demo-app-ts/src/demos/topologyPackageDemo/generator.ts
@@ -99,6 +99,7 @@ export interface GeneratorEdgeOptions {
showAnimations?: boolean;
showTags?: boolean;
terminalTypes?: boolean;
+ freezeEdgeDuringNodeDrag?: boolean;
}
const createNode = (index: number): NodeModel => ({
diff --git a/packages/module/src/components/edges/DefaultEdge.tsx b/packages/module/src/components/edges/DefaultEdge.tsx
index aa38bba9..8dd87482 100644
--- a/packages/module/src/components/edges/DefaultEdge.tsx
+++ b/packages/module/src/components/edges/DefaultEdge.tsx
@@ -1,4 +1,4 @@
-import { useLayoutEffect } from 'react';
+import { useLayoutEffect, useRef } from 'react';
import { observer } from 'mobx-react';
import {
Edge,
@@ -71,6 +71,8 @@ interface DefaultEdgeProps {
canDrop?: boolean;
/** Flag if the node is the current drop target */
dropTarget?: boolean;
+ /** Flag indicating if an edge endpoint is currently being dragged */
+ endpointDragging?: boolean;
/** Flag indicating if the element is selected. Part of WithSelectionProps */
selected?: boolean;
/** Function to call when the element should become selected (or deselected). Part of WithSelectionProps */
@@ -79,6 +81,13 @@ interface DefaultEdgeProps {
onContextMenu?: (e: React.MouseEvent) => void;
/** Flag indicating that the context menu for the edge is currently open */
contextMenuOpen?: boolean;
+ /**
+ * When true, the edge path and terminals stay visually fixed (last position) while
+ * an associated node is being dragged. When false or omitted, the edge updates
+ * during drag as before. Can be set per edge via this prop, element state, or
+ * in the model as edge data: `data: { freezeEdgeDuringNodeDrag: true }`.
+ */
+ freezeEdgeDuringNodeDrag?: boolean;
}
type DefaultEdgeInnerProps = Omit & { element: Edge };
@@ -92,6 +101,7 @@ const DefaultEdgeInner: React.FunctionComponent = observe
dndDropRef,
canDrop,
dropTarget,
+ endpointDragging,
edgeStyle,
animationDuration,
onShowRemoveConnector,
@@ -111,12 +121,23 @@ const DefaultEdgeInner: React.FunctionComponent = observe
className,
selected,
onSelect,
- onContextMenu
+ onContextMenu,
+ freezeEdgeDuringNodeDrag
}) => {
+ const freezeDuringDrag =
+ freezeEdgeDuringNodeDrag ??
+ (element.getData() as { freezeEdgeDuringNodeDrag?: boolean } | undefined)?.freezeEdgeDuringNodeDrag ??
+ false;
+
const [hover, hoverRef] = useHover();
const edgeRef = useCombineRefs(hoverRef, dndDropRef);
const startPoint = element.getStartPoint();
const endPoint = element.getEndPoint();
+ const edgeDRef = useRef(null);
+ const startPointRef = useRef(null);
+ const endPointRef = useRef(null);
+ const targetStartPointRef = useRef(null);
+ const targetEndPointRef = useRef(null);
useLayoutEffect(() => {
if (hover && !dragging) {
@@ -175,6 +196,23 @@ const DefaultEdgeInner: React.FunctionComponent = observe
const tagScale = hover && !(detailsLevel === ScaleDetailsLevel.high) ? Math.max(1, 1 / scale) : 1;
const tagPositionScale = hover && !(detailsLevel === ScaleDetailsLevel.high) ? Math.min(1, scale) : 1;
+ const shouldFreeze = freezeDuringDrag && endpointDragging;
+
+ if (
+ !shouldFreeze ||
+ !edgeDRef.current ||
+ !startPointRef.current ||
+ !endPointRef.current ||
+ !targetStartPointRef.current ||
+ !targetEndPointRef.current
+ ) {
+ edgeDRef.current = d;
+ startPointRef.current = bendpoints[0] || endPoint;
+ endPointRef.current = startPoint;
+ targetStartPointRef.current = bendpoints[bendpoints.length - 1] || startPoint;
+ targetEndPointRef.current = endPoint;
+ }
+
return (
= observe
onMouseEnter={onShowRemoveConnector}
onMouseLeave={onHideRemoveConnector}
/>
-
+
{showTag && (
= observe
terminalType={startTerminalType}
status={startTerminalStatus}
highlight={dragging || hover}
+ startPoint={shouldFreeze ? startPointRef.current : undefined}
+ endPoint={shouldFreeze ? endPointRef.current : undefined}
/>
= observe
terminalType={endTerminalType}
status={endTerminalStatus}
highlight={dragging || hover}
+ startPoint={shouldFreeze ? targetStartPointRef.current : undefined}
+ endPoint={shouldFreeze ? targetEndPointRef.current : undefined}
/>
{children}
diff --git a/packages/module/src/components/factories/components/componentUtils.ts b/packages/module/src/components/factories/components/componentUtils.ts
index ee6e3c46..11bb5be4 100644
--- a/packages/module/src/components/factories/components/componentUtils.ts
+++ b/packages/module/src/components/factories/components/componentUtils.ts
@@ -278,6 +278,19 @@ const edgeDragSourceSpec = (
})
});
+const edgeEndpointIsDragging = (monitor: any, props: EdgeComponentProps) => {
+ if (!monitor.isDragging()) {
+ return false;
+ }
+ if (monitor.getItemType() === NODE_DRAG_TYPE) {
+ return (
+ monitor.getItem().element.id === props.element.getSource().getId() ||
+ monitor.getItem().element.id === props.element.getTarget().getId()
+ );
+ }
+ return false;
+};
+
const edgeDropTargetSpec: DropTargetSpec =
{
accept: [NODE_DRAG_TYPE],
@@ -285,10 +298,11 @@ const edgeDropTargetSpec: DropTargetSpec ({
+ collect: (monitor, props) => ({
droppable: monitor.isDragging(),
dropTarget: monitor.isOver(),
- canDrop: monitor.canDrop()
+ canDrop: monitor.canDrop(),
+ endpointDragging: edgeEndpointIsDragging(monitor, { element: props.element as Edge })
})
};