diff --git a/.changeset/violet-houses-argue.md b/.changeset/violet-houses-argue.md
new file mode 100644
index 00000000..0ae30058
--- /dev/null
+++ b/.changeset/violet-houses-argue.md
@@ -0,0 +1,7 @@
+---
+'@reactflow/background': minor
+'@reactflow/core': minor
+'@reactflow/minimap': minor
+---
+
+edges: add updatable option
diff --git a/examples/vite-app/src/examples/UpdatableEdge/index.tsx b/examples/vite-app/src/examples/UpdatableEdge/index.tsx
index 51760701..f9a109d1 100644
--- a/examples/vite-app/src/examples/UpdatableEdge/index.tsx
+++ b/examples/vite-app/src/examples/UpdatableEdge/index.tsx
@@ -36,7 +36,7 @@ const initialNodes: Node[] = [
>
),
},
- position: { x: 100, y: 100 },
+ position: { x: 75, y: 0 },
},
{
id: '3',
@@ -55,9 +55,46 @@ const initialNodes: Node[] = [
width: 180,
},
},
+ {
+ id: '4',
+ data: {
+ label: (
+ <>
+ Node D
+ >
+ ),
+ },
+ position: { x: -75, y: 100 },
+ },
+ {
+ id: '5',
+ data: {
+ label: (
+ <>
+ Node E
+ >
+ ),
+ },
+ position: { x: 150, y: 100 },
+ },
+ {
+ id: '6',
+ data: {
+ label: (
+ <>
+ Node F
+ >
+ ),
+ },
+ position: { x: 150, y: 250 },
+ },
];
-const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }];
+const initialEdges: Edge[] = [
+ { id: 'e1-3', source: '1', target: '3', label: 'This edge can only be updated from source', updatable: 'source' },
+ { id: 'e2-4', source: '2', target: '4', label: 'This edge can only be updated from target', updatable: 'target' },
+ { 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) =>
diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx
index 607f2c5a..cb7d987f 100644
--- a/packages/core/src/components/Edges/wrapEdge.tsx
+++ b/packages/core/src/components/Edges/wrapEdge.tsx
@@ -55,6 +55,7 @@ export default (EdgeComponent: ComponentType) => {
rfId,
ariaLabel,
isFocusable,
+ isUpdatable,
pathOptions,
interactionWidth,
}: WrapEdgeProps): JSX.Element | null => {
@@ -138,7 +139,6 @@ export default (EdgeComponent: ComponentType) => {
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
const inactive = !elementsSelectable && !onClick;
- const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined';
const onKeyDown = (event: KeyboardEvent) => {
if (elementSelectionKeys.includes(event.key) && elementsSelectable) {
@@ -205,28 +205,32 @@ export default (EdgeComponent: ComponentType) => {
interactionWidth={interactionWidth}
/>
)}
- {handleEdgeUpdate && (
+ {isUpdatable && (
<>
-
-
+ {(isUpdatable === 'source' || isUpdatable === true) && (
+
+ )}
+ {(isUpdatable === 'target' || isUpdatable === true) && (
+
+ )}
>
)}
diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx
index 4491bb94..e31fba17 100644
--- a/packages/core/src/components/StoreUpdater/index.tsx
+++ b/packages/core/src/components/StoreUpdater/index.tsx
@@ -20,6 +20,7 @@ type StoreUpdaterProps = Pick<
| 'nodesConnectable'
| 'nodesFocusable'
| 'edgesFocusable'
+ | 'edgesUpdatable'
| 'minZoom'
| 'maxZoom'
| 'nodeExtent'
@@ -98,6 +99,7 @@ const StoreUpdater = ({
nodesConnectable,
nodesFocusable,
edgesFocusable,
+ edgesUpdatable,
elevateNodesOnSelect,
minZoom,
maxZoom,
@@ -162,6 +164,7 @@ const StoreUpdater = ({
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
+ useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState);
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState);
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
diff --git a/packages/core/src/container/EdgeRenderer/index.tsx b/packages/core/src/container/EdgeRenderer/index.tsx
index 09b8f0c6..9f7a2f1b 100644
--- a/packages/core/src/container/EdgeRenderer/index.tsx
+++ b/packages/core/src/container/EdgeRenderer/index.tsx
@@ -39,6 +39,7 @@ type EdgeRendererProps = Pick<
const selector = (s: ReactFlowState) => ({
nodesConnectable: s.nodesConnectable,
edgesFocusable: s.edgesFocusable,
+ edgesUpdatable: s.edgesUpdatable,
elementsSelectable: s.elementsSelectable,
width: s.width,
height: s.height,
@@ -66,10 +67,8 @@ const EdgeRenderer = ({
onEdgeUpdateEnd,
children,
}: EdgeRendererProps) => {
- const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(
- selector,
- shallow
- );
+ const { edgesFocusable, edgesUpdatable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } =
+ useStore(selector, shallow);
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
if (!width) {
@@ -114,6 +113,9 @@ const EdgeRenderer = ({
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
+ const isUpdatable =
+ typeof onEdgeUpdate !== 'undefined' &&
+ (edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
if (!sourceHandle || !targetHandle) {
onError?.('008', errorMessages['error008'](sourceHandle, edge));
@@ -173,6 +175,7 @@ const EdgeRenderer = ({
rfId={rfId}
ariaLabel={edge.ariaLabel}
isFocusable={isFocusable}
+ isUpdatable={isUpdatable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
interactionWidth={edge.interactionWidth}
/>
diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx
index 9fa23f9c..1d36123d 100644
--- a/packages/core/src/container/ReactFlow/index.tsx
+++ b/packages/core/src/container/ReactFlow/index.tsx
@@ -115,6 +115,7 @@ const ReactFlow = forwardRef(
nodesFocusable,
nodeOrigin = initNodeOrigin,
edgesFocusable,
+ edgesUpdatable,
elementsSelectable,
defaultViewport = initDefaultViewport,
minZoom = 0.5,
@@ -266,6 +267,7 @@ const ReactFlow = forwardRef(
nodesConnectable={nodesConnectable}
nodesFocusable={nodesFocusable}
edgesFocusable={edgesFocusable}
+ edgesUpdatable={edgesUpdatable}
elementsSelectable={elementsSelectable}
elevateNodesOnSelect={elevateNodesOnSelect}
minZoom={minZoom}
diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts
index 8c4a5d76..2d1bb93d 100644
--- a/packages/core/src/store/initialState.ts
+++ b/packages/core/src/store/initialState.ts
@@ -46,6 +46,7 @@ const initialState: ReactFlowStore = {
nodesConnectable: true,
nodesFocusable: true,
edgesFocusable: true,
+ edgesUpdatable: true,
elementsSelectable: true,
elevateNodesOnSelect: true,
fitViewOnInit: false,
diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts
index cae9da3b..46364257 100644
--- a/packages/core/src/types/component-props.ts
+++ b/packages/core/src/types/component-props.ts
@@ -112,6 +112,7 @@ export type ReactFlowProps = HTMLAttributes & {
nodesFocusable?: boolean;
nodeOrigin?: NodeOrigin;
edgesFocusable?: boolean;
+ edgesUpdatable?: boolean;
initNodeOrigin?: NodeOrigin;
elementsSelectable?: boolean;
selectNodesOnDrag?: boolean;
diff --git a/packages/core/src/types/edges.ts b/packages/core/src/types/edges.ts
index 143c7fa6..7358b0e8 100644
--- a/packages/core/src/types/edges.ts
+++ b/packages/core/src/types/edges.ts
@@ -36,8 +36,11 @@ type DefaultEdge = {
ariaLabel?: string;
interactionWidth?: number;
focusable?: boolean;
+ updatable?: EdgeUpdatable;
} & EdgeLabelOptions;
+export type EdgeUpdatable = boolean | HandleType;
+
export type SmoothStepPathOptions = {
offset?: number;
borderRadius?: number;
@@ -88,6 +91,7 @@ export type WrapEdgeProps = Omit, 'sourceHandle' | 'targetHandl
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
rfId?: string;
isFocusable: boolean;
+ isUpdatable: EdgeUpdatable;
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
};
diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts
index aeb33632..f57a1ea7 100644
--- a/packages/core/src/types/general.ts
+++ b/packages/core/src/types/general.ts
@@ -182,6 +182,7 @@ export type ReactFlowStore = {
nodesConnectable: boolean;
nodesFocusable: boolean;
edgesFocusable: boolean;
+ edgesUpdatable: boolean;
elementsSelectable: boolean;
elevateNodesOnSelect: boolean;