diff --git a/examples/react/cypress/components/hooks/useEdges.cy.tsx b/examples/react/cypress/components/hooks/useEdges.cy.tsx index 34b0ae3c..c1123d4d 100644 --- a/examples/react/cypress/components/hooks/useEdges.cy.tsx +++ b/examples/react/cypress/components/hooks/useEdges.cy.tsx @@ -13,7 +13,6 @@ describe('useEdges.cy.tsx', () => { ); - cy.get('@onChangeSpy').should('have.been.calledWith', []); cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges); }); diff --git a/examples/react/cypress/components/hooks/useNodes.cy.tsx b/examples/react/cypress/components/hooks/useNodes.cy.tsx index 4f813e00..9c10ad70 100644 --- a/examples/react/cypress/components/hooks/useNodes.cy.tsx +++ b/examples/react/cypress/components/hooks/useNodes.cy.tsx @@ -15,8 +15,10 @@ const initialNodes: Node[] = nodes.map((n) => ({ const expectedNodes: Node[] = initialNodes.map((n) => ({ ...n, - positionAbsolute: n.position, - ...nodeDimensions, + computed: { + positionAbsolute: n.position, + ...nodeDimensions, + }, })); describe('useNodes.cy.tsx', () => { @@ -29,7 +31,6 @@ describe('useNodes.cy.tsx', () => { ); - cy.get('@onChangeSpy').should('have.been.calledWith', []); cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes); }); diff --git a/examples/react/cypress/components/utils/apply-changes.cy.ts b/examples/react/cypress/components/utils/apply-changes.cy.ts index 9bdf01af..1548b7e9 100644 --- a/examples/react/cypress/components/utils/apply-changes.cy.ts +++ b/examples/react/cypress/components/utils/apply-changes.cy.ts @@ -156,20 +156,22 @@ describe('applyChanges Testing', () => { expect(nextNodes[0].computed).to.be.deep.equal({ width: newWidth, height: newHeight }); }); - it('resets nodes/edges', () => { + it('replaces nodes/edges', () => { const nodesLength = nodes.length; - const nodeChanges: NodeChange[] = [{ type: 'reset', item: nodes[0] }]; + const nodeChanges: NodeChange[] = [{ type: 'replace', id: nodes[0].id, item: nodes[1] }]; const nextNodes = applyNodeChanges(nodeChanges, nodes); expect(nodes.length).length.to.be.equal(nodesLength); - expect(nextNodes.length).to.be.equal(nodeChanges.length); + expect(nextNodes.length).to.be.equal(nodesLength); + expect(nextNodes[0]).to.be.deep.equal(nodes[1]); const edgesLength = edges.length; - const edgeChange: EdgeChange[] = [{ type: 'reset', item: edges[0] }]; + const edgeChange: EdgeChange[] = [{ type: 'replace', id: edges[0].id, item: edges[1] }]; const nextEdges = applyEdgeChanges(edgeChange, edges); expect(edges.length).length.to.be.equal(edgesLength); - expect(nextEdges.length).to.be.equal(edgeChange.length); + expect(nextEdges.length).to.be.equal(edgesLength); + expect(nextEdges[0]).to.be.deep.equal(edges[1]); }); }); diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 2f3d55ba..76ab08e4 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -21,6 +21,7 @@ import Interaction from '../examples/Interaction'; import Intersection from '../examples/Intersection'; import Layouting from '../examples/Layouting'; import MultiFlows from '../examples/MultiFlows'; +import MultiSetNodes from '../examples/MultiSetNodes'; import NodeResizer from '../examples/NodeResizer'; import NodeTypeChange from '../examples/NodeTypeChange'; import NodeTypesObjectChange from '../examples/NodeTypesObjectChange'; @@ -180,6 +181,11 @@ const routes: IRoute[] = [ path: 'layouting', component: Layouting, }, + { + name: 'Multi setNodes', + path: 'multi-setnodes', + component: MultiSetNodes, + }, { name: 'Multi Flows', path: 'multiflows', diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 4f1a51d2..8d9c7dfd 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -1,4 +1,4 @@ -import { MouseEvent } from 'react'; +import { MouseEvent, useCallback } from 'react'; import { ReactFlow, MiniMap, @@ -53,10 +53,11 @@ const initialEdges: Edge[] = [ const defaultEdgeOptions = {}; const BasicFlow = () => { - const instance = useReactFlow(); + const { setNodes, getNodes, setEdges, getEdges, deleteElements, updateNodeData, toObject, setViewport } = + useReactFlow(); const updatePos = () => { - instance.setNodes((nodes) => + setNodes((nodes) => nodes.map((node) => { node.position = { x: Math.random() * 400, @@ -68,11 +69,11 @@ const BasicFlow = () => { ); }; - const logToObject = () => console.log(instance.toObject()); - const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 }); + const logToObject = () => console.log(toObject()); + const resetTransform = () => setViewport({ x: 0, y: 0, zoom: 1 }); const toggleClassnames = () => { - instance.setNodes((nodes) => + setNodes((nodes) => nodes.map((node) => { node.className = node.className === 'light' ? 'dark' : 'light'; @@ -81,6 +82,30 @@ const BasicFlow = () => { ); }; + const deleteSelectedElements = useCallback(() => { + const selectedNodes = getNodes().filter((node) => node.selected); + const selectedEdges = getEdges().filter((edge) => edge.selected); + deleteElements({ nodes: selectedNodes, edges: selectedEdges }); + }, [deleteElements]); + + const deleteSomeElements = useCallback(() => { + deleteElements({ nodes: [{ id: '2' }], edges: [{ id: 'e1-3' }] }); + }, []); + + const onSetNodes = () => { + setNodes([ + { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node a' } }, + { id: 'b', position: { x: 0, y: 150 }, data: { label: 'Node b' } }, + ]); + + setEdges([{ id: 'a-b', source: 'a', target: 'b' }]); + }; + + const onUpdateNode = () => { + updateNodeData('1', { label: 'update' }); + updateNodeData('2', { label: 'update' }); + }; + return ( { + + + + + ); diff --git a/examples/react/src/examples/MultiSetNodes/index.tsx b/examples/react/src/examples/MultiSetNodes/index.tsx new file mode 100644 index 00000000..fcb49924 --- /dev/null +++ b/examples/react/src/examples/MultiSetNodes/index.tsx @@ -0,0 +1,82 @@ +import { useCallback } from 'react'; +import { + ReactFlow, + Controls, + addEdge, + Connection, + useNodesState, + useEdgesState, + Background, + Node, + Edge, + Panel, + useReactFlow, + ReactFlowProvider, +} from '@xyflow/react'; + +import './style.css'; + +const initNodes: Node[] = []; + +for (let i = 0; i < 100; i++) { + initNodes.push({ + id: i.toString(), + data: { + label: `node ${i + 1}`, + }, + position: { x: (i % 10) * 60, y: Math.floor(i / 10) * 60 }, + }); +} + +const initEdges: Edge[] = []; + +const CustomNodeFlow = () => { + const { setNodes, updateNodeData } = useReactFlow(); + const [nodes, , onNodesChange] = useNodesState(initNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges); + + const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]); + + const multiSetNodes = () => { + nodes.forEach((node) => + setNodes((nds) => + nds.map((n) => { + if (n.id === node.id) { + return { ...n, data: { label: 'node set' } }; + } + + return n; + }) + ) + ); + }; + + const multiUpdateNodes = () => { + nodes.forEach((node) => updateNodeData(node.id, { label: 'node update' })); + }; + + return ( + + + + + + + + + ); +}; + +export default () => ( + + + +); diff --git a/examples/react/src/examples/MultiSetNodes/style.css b/examples/react/src/examples/MultiSetNodes/style.css new file mode 100644 index 00000000..2f427666 --- /dev/null +++ b/examples/react/src/examples/MultiSetNodes/style.css @@ -0,0 +1,3 @@ +.multiset .react-flow__node { + width: 50px; +} diff --git a/examples/react/src/examples/UseNodesData/TextNode.tsx b/examples/react/src/examples/UseNodesData/TextNode.tsx index 6ca424e6..113d41fb 100644 --- a/examples/react/src/examples/UseNodesData/TextNode.tsx +++ b/examples/react/src/examples/UseNodesData/TextNode.tsx @@ -1,16 +1,21 @@ -import { memo, ChangeEventHandler } from 'react'; +import { memo, useState } from 'react'; import { Position, NodeProps, Handle, useReactFlow } from '@xyflow/react'; function TextNode({ id, data }: NodeProps) { const { updateNodeData } = useReactFlow(); - - const onChange: ChangeEventHandler = (evt) => updateNodeData(id, { text: evt.target.value }); + const [text, setText] = useState(data.text); + const updateText = (text: string) => { + // avoid jumping caret with a synchronous update + setText(text); + // update actual node data + updateNodeData(id, { text }); + }; return (
node {id}
- + updateText(evt.target.value)} value={text} />
diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index 7bb1a94e..d6b697a9 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -78,7 +78,7 @@ const initEdges: Edge[] = [ ]; const CustomNodeFlow = () => { - const [nodes, setNodes, onNodesChange] = useNodesState(initNodes); + const [nodes, , onNodesChange] = useNodesState(initNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges); const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]); diff --git a/examples/react/src/examples/UseReactFlow/index.tsx b/examples/react/src/examples/UseReactFlow/index.tsx index 69f3c762..bed127fb 100644 --- a/examples/react/src/examples/UseReactFlow/index.tsx +++ b/examples/react/src/examples/UseReactFlow/index.tsx @@ -1,4 +1,4 @@ -import { useCallback, MouseEvent, useEffect } from 'react'; +import { useCallback, MouseEvent, useEffect, useRef } from 'react'; import { ReactFlow, Background, @@ -68,6 +68,7 @@ const UseZoomPanHelperFlow = () => { getNodes, getEdges, deleteElements, + updateNodeData, } = useReactFlow(); const onPaneClick = useCallback( @@ -125,12 +126,31 @@ const UseZoomPanHelperFlow = () => { deleteElements({ nodes: [{ id: '2' }], edges: [{ id: 'e1-3' }] }); }, []); + const edgeAdded = useRef(false); + useEffect(() => { - addEdges({ id: 'e3-4', source: '3', target: '4' }); + if (!edgeAdded.current) { + addEdges({ id: 'e3-4', source: '3', target: '4' }); + edgeAdded.current = true; + } }, [addEdges]); const onResetNodes = useCallback(() => setNodesHook(initialNodes), [setNodesHook]); + const onSetNodes = () => { + setNodes([ + { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node a' } }, + { id: 'b', position: { x: 0, y: 150 }, data: { label: 'Node b' } }, + ]); + + setEdges([{ id: 'a-b', source: 'a', target: 'b' }]); + }; + + const onUpdateNode = () => { + updateNodeData('1', { label: 'update' }); + updateNodeData('2', { label: 'update' }); + }; + return ( { + + diff --git a/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte b/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte index a195cf1c..0a27afd9 100644 --- a/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte +++ b/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte @@ -1,5 +1,5 @@