diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts
index 6dffe3b0..28c9e8cc 100644
--- a/examples/react/src/App/routes.ts
+++ b/examples/react/src/App/routes.ts
@@ -53,6 +53,7 @@ import UseHandleConnections from '../examples/UseHandleConnections';
import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop';
import DevTools from '../examples/DevTools';
import Redux from '../examples/Redux';
+import MovingHandles from '../examples/MovingHandles';
export interface IRoute {
name: string;
@@ -206,6 +207,11 @@ const routes: IRoute[] = [
path: 'multi-setnodes',
component: MultiSetNodes,
},
+ {
+ name: 'Moving Handles',
+ path: 'moving-handles',
+ component: MovingHandles,
+ },
{
name: 'Multi Flows',
path: 'multiflows',
diff --git a/examples/react/src/examples/EasyConnect/CustomNode.tsx b/examples/react/src/examples/EasyConnect/CustomNode.tsx
index 03726da2..803f7b9e 100644
--- a/examples/react/src/examples/EasyConnect/CustomNode.tsx
+++ b/examples/react/src/examples/EasyConnect/CustomNode.tsx
@@ -2,9 +2,7 @@ import { Handle, NodeProps, Position, useConnection } from '@xyflow/react';
export default function CustomNode({ id }: NodeProps) {
const connection = useConnection();
-
const isTarget = connection.inProgress && connection.fromNode.id !== id;
-
const label = isTarget ? 'Drop here' : 'Drag to connect';
return (
@@ -17,12 +15,12 @@ export default function CustomNode({ id }: NodeProps) {
}}
>
{/* If handles are conditionally rendered and not present initially, you need to update the node internals https://reactflow.dev/docs/api/hooks/use-update-node-internals/ */}
- {/* In this case we don't need to use useUpdateNodeInternals, since !connection.inProgress is true at the beginning and all handles are rendered initially. */}
- {!connection.inProgress && }
-
+ {/* In this case we don't need to use useUpdateNodeInternals, since !isConnecting is true at the beginning and all handles are rendered initially. */}
+ {!connection.inProgress && }
{/* We want to disable the target handle, if the connection was started from this node */}
- {/* {(!connection.inProgress || isTarget) && ( */}
-
+ {(!connection.inProgress || isTarget) && (
+
+ )}
{label}
diff --git a/examples/react/src/examples/MovingHandles/MovingHandleNode.tsx b/examples/react/src/examples/MovingHandles/MovingHandleNode.tsx
new file mode 100644
index 00000000..bef0bec9
--- /dev/null
+++ b/examples/react/src/examples/MovingHandles/MovingHandleNode.tsx
@@ -0,0 +1,57 @@
+import React, { memo, CSSProperties } from 'react';
+import { Handle, Position, NodeProps, useConnection } from '@xyflow/react';
+
+import type { MovingHandleNode } from '.';
+
+const sourceHandleStyle: CSSProperties = {
+ position: 'relative',
+ transform: 'translate(-50%, 0)',
+ top: 0,
+ transition: 'transform 0.5s',
+};
+
+function MovingHandleNode({}: NodeProps) {
+ const connection = useConnection();
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
+
+export default memo(MovingHandleNode);
diff --git a/examples/react/src/examples/MovingHandles/index.tsx b/examples/react/src/examples/MovingHandles/index.tsx
new file mode 100644
index 00000000..8e254eb2
--- /dev/null
+++ b/examples/react/src/examples/MovingHandles/index.tsx
@@ -0,0 +1,116 @@
+import { useState, useCallback, useEffect } from 'react';
+import {
+ ReactFlow,
+ Controls,
+ addEdge,
+ Node,
+ Position,
+ useEdgesState,
+ Background,
+ applyNodeChanges,
+ OnNodesChange,
+ OnConnect,
+ BuiltInNode,
+ BuiltInEdge,
+ NodeTypes,
+ ReactFlowProvider,
+ useConnection,
+ useReactFlow,
+ useUpdateNodeInternals,
+} from '@xyflow/react';
+
+import MovingHandleNode from './MovingHandleNode';
+
+export type MovingHandleNode = Node<{}, 'movingHandle'>;
+export type MyNode = BuiltInNode | MovingHandleNode;
+export type MyEdge = BuiltInEdge;
+
+const nodeTypes: NodeTypes = {
+ movingHandle: MovingHandleNode,
+};
+
+const initNodes: MyNode[] = [
+ {
+ id: 'input',
+ type: 'input',
+ data: { label: 'input' },
+ position: { x: -300, y: 0 },
+ sourcePosition: Position.Right,
+ },
+];
+
+for (let i = 0; i < 10; i++) {
+ initNodes.push({
+ id: `${i}`,
+ type: 'movingHandle',
+ position: { x: 0, y: i * 60 },
+ data: {},
+ });
+}
+
+const initEdges: MyEdge[] = [];
+
+const CustomNodeFlow = () => {
+ const [nodes, setNodes] = useState(initNodes);
+
+ const onNodesChange: OnNodesChange = useCallback(
+ (changes) =>
+ setNodes((nds) => {
+ const nextNodes = applyNodeChanges(changes, nds);
+ return nextNodes;
+ }),
+ [setNodes]
+ );
+
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
+
+ const onConnect: OnConnect = useCallback(
+ (connection) => setEdges((eds) => addEdge({ ...connection, animated: true }, eds)),
+ [setEdges]
+ );
+
+ return (
+
+
+
+
+
+ );
+};
+
+function NodeUpdater() {
+ const connection = useConnection();
+ const { getNodes } = useReactFlow();
+ const updateNodeInternals = useUpdateNodeInternals();
+
+ useEffect(() => {
+ const startTime = Date.now();
+ const nodeIds = getNodes().map((n) => n.id);
+
+ function update() {
+ if (Date.now() - startTime < 500) {
+ updateNodeInternals(nodeIds);
+ requestAnimationFrame(update);
+ }
+ }
+
+ update();
+ }, [connection.inProgress]);
+
+ return null;
+}
+
+export default () => (
+
+
+
+);