From babaa782ea28b435121ce37921dca1161df8d2bd Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 14 Aug 2024 16:30:01 +0200 Subject: [PATCH] chore(examples): add moving handle example --- examples/react/src/App/routes.ts | 6 + .../src/examples/EasyConnect/CustomNode.tsx | 12 +- .../MovingHandles/MovingHandleNode.tsx | 57 +++++++++ .../src/examples/MovingHandles/index.tsx | 116 ++++++++++++++++++ 4 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 examples/react/src/examples/MovingHandles/MovingHandleNode.tsx create mode 100644 examples/react/src/examples/MovingHandles/index.tsx 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 ( + <> +
+ + +
+
+
moving handles
+ + +
+ + ); +} + +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 () => ( + + + +);