xyflow/xyflow/main 322k tokens More Tools
```
├── .changeset/
   ├── README.md (100 tokens)
   ├── config.json (100 tokens)
   ├── sharp-toys-cheer.md
├── .coderabbit.yaml (5.5k tokens)
├── .gitattributes (omitted)
├── .github/
   ├── FUNDING.yml
   ├── ISSUE_TEMPLATE/
      ├── bug_report.yml (700 tokens)
      ├── config.yml
      ├── feature_request.yml (100 tokens)
   ├── actions/
      ├── ci-checks/
         ├── action.yml
      ├── ci-setup/
         ├── action.yml (100 tokens)
   ├── workflows/
      ├── dispatchWebsiteUpdate.yaml (200 tokens)
      ├── playwright.yml (100 tokens)
      ├── release.yml (100 tokens)
├── .gitignore
├── .npmrc
├── .prettierignore (omitted)
├── .prettierrc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md (600 tokens)
├── CONTRIBUTING.md (1000 tokens)
├── LICENSE (omitted)
├── README.md (1100 tokens)
├── SECURITY.md (400 tokens)
├── dependabot.yml
├── examples/
   ├── README.md (100 tokens)
   ├── astro-xyflow/
      ├── .gitignore
      ├── README.md
      ├── astro.config.mjs
      ├── package.json (100 tokens)
      ├── public/
         ├── .gitkeep
      ├── src/
         ├── components/
            ├── ReactFlowExample/
               ├── CustomNode.tsx (200 tokens)
               ├── index.tsx (500 tokens)
            ├── ReactFlowInitialExample/
               ├── CustomNode.tsx (100 tokens)
               ├── index.tsx (300 tokens)
            ├── SvelteFlowExample/
               ├── index.svelte (500 tokens)
            ├── SvelteFlowInitialExample/
               ├── CustomNode.svelte (100 tokens)
               ├── index.svelte (200 tokens)
         ├── env.d.ts (omitted)
         ├── pages/
            ├── index.astro (200 tokens)
      ├── tsconfig.json
   ├── react/
      ├── .gitignore (100 tokens)
      ├── README.md (200 tokens)
      ├── cypress.config.ts (100 tokens)
      ├── cypress/
         ├── components/
            ├── hooks/
               ├── useEdges.cy.tsx (200 tokens)
               ├── useNodes.cy.tsx (300 tokens)
               ├── useNodesInitialized.cy.tsx (400 tokens)
               ├── useOnViewportChange.cy.tsx (600 tokens)
               ├── useViewport.cy.tsx (400 tokens)
            ├── reactflow/
               ├── basic-props.cy.tsx (1300 tokens)
               ├── event-handlers.cy.tsx (1400 tokens)
               ├── multiple-instance.cy.tsx (400 tokens)
               ├── on-nodes-change.cy.tsx (800 tokens)
               ├── uncontrolled.cy.tsx (300 tokens)
               ├── view-props.cy.tsx (2000 tokens)
            ├── utils/
               ├── adopt-user-nodes.cy.ts (1400 tokens)
               ├── apply-changes.cy.ts (1300 tokens)
               ├── get-nodes-bounds.cy.ts (800 tokens)
               ├── graph-utils.cy.ts (500 tokens)
         ├── e2e/
            ├── basic.cy.ts (1100 tokens)
            ├── controls.cy.ts (500 tokens)
            ├── draghandle.cy.ts (200 tokens)
            ├── empty.cy.ts (200 tokens)
            ├── figma.cy.ts (300 tokens)
            ├── hidden.cy.ts (200 tokens)
            ├── interaction.cy.ts (1000 tokens)
            ├── minimap.cy.ts (600 tokens)
         ├── fixtures/
            ├── simpleflow.ts (100 tokens)
         ├── support/
            ├── ControlledFlow.tsx (300 tokens)
            ├── commands.ts (400 tokens)
            ├── component-index.html (100 tokens)
            ├── component.ts (200 tokens)
            ├── e2e.ts (200 tokens)
      ├── index.html (100 tokens)
      ├── package.json (300 tokens)
      ├── public/
         ├── favicon.ico
      ├── src/
         ├── App/
            ├── header.tsx (200 tokens)
            ├── index.tsx (100 tokens)
            ├── routes.ts (1800 tokens)
         ├── app.d.ts (omitted)
         ├── examples/
            ├── A11y/
               ├── index.tsx (600 tokens)
            ├── AddNodeOnEdgeDrop/
               ├── index.tsx (500 tokens)
            ├── Backgrounds/
               ├── index.tsx (300 tokens)
               ├── style.module.css
            ├── Basic/
               ├── index.tsx (1100 tokens)
            ├── BrokenNodes/
               ├── index.tsx (400 tokens)
            ├── CancelConnection/
               ├── Timer.module.css (100 tokens)
               ├── Timer.tsx (100 tokens)
               ├── data.ts (100 tokens)
               ├── hooks/
                  ├── useCountdown.ts (100 tokens)
               ├── index.tsx (300 tokens)
            ├── ClickDistance/
               ├── index.tsx (400 tokens)
            ├── ColorMode/
               ├── index.tsx (400 tokens)
               ├── style.css
            ├── ControlledUncontrolled/
               ├── index.tsx (600 tokens)
            ├── ControlledViewport/
               ├── index.tsx (500 tokens)
            ├── CustomConnectionLine/
               ├── ConnectionLine.tsx (100 tokens)
               ├── index.tsx (200 tokens)
            ├── CustomMiniMapNode/
               ├── index.tsx (500 tokens)
            ├── CustomNode/
               ├── ColorSelectorNode.tsx (300 tokens)
               ├── index.tsx (900 tokens)
            ├── DefaultEdgeOverwrite/
               ├── index.tsx (300 tokens)
            ├── DefaultNodeOverwrite/
               ├── index.tsx (200 tokens)
            ├── DefaultNodes/
               ├── index.tsx (400 tokens)
            ├── DetachedHandle/
               ├── index.tsx (200 tokens)
               ├── style.css
            ├── DevTools/
               ├── DevTools/
                  ├── ChangeLogger.tsx (400 tokens)
                  ├── NodeInspector.tsx (300 tokens)
                  ├── index.tsx (300 tokens)
                  ├── style.css (300 tokens)
               ├── index.tsx (200 tokens)
            ├── DragHandle/
               ├── DragHandleNode.tsx (200 tokens)
               ├── index.tsx (200 tokens)
            ├── DragNDrop/
               ├── Sidebar.tsx (200 tokens)
               ├── dnd.module.css (100 tokens)
               ├── index.tsx (400 tokens)
            ├── EasyConnect/
               ├── CustomConnectionLine.tsx (100 tokens)
               ├── CustomNode.tsx (300 tokens)
               ├── FloatingEdge.tsx (100 tokens)
               ├── index.tsx (400 tokens)
               ├── style.css (100 tokens)
               ├── utils.tsx (700 tokens)
            ├── EdgeRenderer/
               ├── CustomEdge.tsx (300 tokens)
               ├── CustomEdge2.tsx (200 tokens)
               ├── index.tsx (1000 tokens)
            ├── EdgeRouting/
               ├── index.tsx (1300 tokens)
            ├── EdgeToolbar/
               ├── CustomEdge.tsx (200 tokens)
               ├── index.tsx (300 tokens)
            ├── EdgeTypes/
               ├── index.tsx (300 tokens)
               ├── utils.ts (600 tokens)
            ├── Edges/
               ├── CustomEdge.tsx (100 tokens)
               ├── CustomEdge2.tsx (200 tokens)
               ├── CustomEdge3.css (100 tokens)
               ├── CustomEdge3.tsx (100 tokens)
               ├── index.tsx (1500 tokens)
            ├── Empty/
               ├── index.tsx (400 tokens)
            ├── Figma/
               ├── index.tsx (400 tokens)
            ├── FloatingEdges/
               ├── FloatingConnectionLine.tsx (200 tokens)
               ├── FloatingEdge.tsx (200 tokens)
               ├── index.tsx (300 tokens)
               ├── style.module.css
               ├── utils.ts (700 tokens)
            ├── Hidden/
               ├── index.tsx (500 tokens)
            ├── Interaction/
               ├── index.tsx (1700 tokens)
            ├── InteractiveMinimap/
               ├── index.tsx (800 tokens)
            ├── Intersection/
               ├── index.tsx (500 tokens)
               ├── style.css
            ├── Layouting/
               ├── index.tsx (600 tokens)
               ├── initial-elements.ts (400 tokens)
               ├── layouting.module.css
            ├── MovingHandles/
               ├── MovingHandleNode.tsx (300 tokens)
               ├── index.tsx (500 tokens)
            ├── MultiFlows/
               ├── index.tsx (300 tokens)
               ├── multiflows.module.css
            ├── MultiSetNodes/
               ├── index.tsx (400 tokens)
               ├── style.css
            ├── NodeResizer/
               ├── BottomRightResizer.tsx (100 tokens)
               ├── CustomResizer.tsx (200 tokens)
               ├── DefaultResizer.tsx (200 tokens)
               ├── HorizontalResizer.tsx (300 tokens)
               ├── ResizeIcon.tsx (100 tokens)
               ├── VerticalResizer.tsx (300 tokens)
               ├── index.tsx (1000 tokens)
            ├── NodeToolbar/
               ├── CustomNode.tsx (100 tokens)
               ├── SelectedNodesToolbar.tsx (100 tokens)
               ├── index.tsx (300 tokens)
            ├── NodeTypeChange/
               ├── index.tsx (300 tokens)
            ├── NodeTypesObjectChange/
               ├── index.tsx (400 tokens)
            ├── Overview/
               ├── index.tsx (1500 tokens)
            ├── Provider/
               ├── Sidebar.tsx (200 tokens)
               ├── index.tsx (400 tokens)
               ├── provider.module.css (100 tokens)
            ├── ReconnectEdge/
               ├── index.tsx (600 tokens)
            ├── Redux/
               ├── index.tsx (200 tokens)
               ├── initial-elements.tsx (300 tokens)
               ├── state.ts (300 tokens)
            ├── SaveRestore/
               ├── Controls.tsx (400 tokens)
               ├── index.tsx (200 tokens)
               ├── save.module.css
            ├── SetNodesBatching/
               ├── index.tsx (400 tokens)
            ├── Stress/
               ├── index.tsx (1700 tokens)
               ├── performanceUtils.ts (900 tokens)
               ├── utils.ts (200 tokens)
            ├── Subflow/
               ├── DebugNode.tsx (200 tokens)
               ├── index.tsx (1200 tokens)
            ├── Switch/
               ├── index.tsx (700 tokens)
            ├── TouchDevice/
               ├── index.tsx (300 tokens)
               ├── touch-device.css (100 tokens)
            ├── Undirectional/
               ├── CustomNode.tsx (100 tokens)
               ├── index.tsx (900 tokens)
            ├── UpdateNode/
               ├── index.tsx (600 tokens)
               ├── updatenode.module.css (100 tokens)
            ├── UseConnection/
               ├── index.tsx (300 tokens)
            ├── UseKeyPress/
               ├── index.tsx (100 tokens)
            ├── UseNodeConnections/
               ├── MultiHandleNode.tsx (300 tokens)
               ├── SingleHandleNode.tsx (300 tokens)
               ├── index.tsx (400 tokens)
            ├── UseNodesData/
               ├── ResultNode.tsx (100 tokens)
               ├── TextNode.tsx (200 tokens)
               ├── UppercaseNode.tsx (200 tokens)
               ├── index.tsx (500 tokens)
            ├── UseNodesInit/
               ├── index.tsx (400 tokens)
            ├── UseOnSelectionChange/
               ├── CustomNode.tsx (200 tokens)
               ├── index.tsx (400 tokens)
            ├── UseReactFlow/
               ├── index.tsx (1100 tokens)
            ├── UseUpdateNodeInternals/
               ├── CustomNode.tsx (200 tokens)
               ├── index.tsx (300 tokens)
            ├── Validation/
               ├── ConnectionStatus.tsx (300 tokens)
               ├── index.tsx (600 tokens)
               ├── validation.module.css (200 tokens)
         ├── generic-tests/
            ├── Flow.tsx (300 tokens)
            ├── edges/
               ├── general.ts (800 tokens)
            ├── index.tsx (100 tokens)
            ├── node-toolbar/
               ├── components/
                  ├── ToolbarNode.tsx (100 tokens)
               ├── general.ts (200 tokens)
            ├── nodes/
               ├── components/
                  ├── DragHandleNode.tsx (100 tokens)
               ├── general.ts (400 tokens)
            ├── pane/
               ├── general.ts (100 tokens)
               ├── non-defaults.ts (100 tokens)
         ├── index.css (200 tokens)
         ├── main.tsx (100 tokens)
         ├── vite-env.d.ts (omitted)
      ├── tsconfig.json (100 tokens)
      ├── tsconfig.node.json
      ├── vite.config.ts (100 tokens)
   ├── svelte/
      ├── .eslintignore
      ├── .eslintrc.cjs (100 tokens)
      ├── .gitignore
      ├── .npmrc
      ├── .prettierignore
      ├── .prettierrc
      ├── README.md (200 tokens)
      ├── package.json (200 tokens)
      ├── src/
         ├── app.d.ts (omitted)
         ├── app.html (100 tokens)
         ├── components/
            ├── Header/
               ├── Header.svelte (200 tokens)
               ├── index.ts
         ├── generic-tests/
            ├── edges/
               ├── general.ts (700 tokens)
            ├── node-toolbar/
               ├── components/
                  ├── ToolbarNode.svelte (100 tokens)
               ├── general.ts (200 tokens)
            ├── nodes/
               ├── components/
                  ├── DragHandleNode.svelte (100 tokens)
               ├── general.ts (400 tokens)
            ├── pane/
               ├── activation-keys.ts (100 tokens)
               ├── general.ts (100 tokens)
               ├── non-defaults.ts (100 tokens)
         ├── routes/
            ├── +layout.server.ts
            ├── +layout.ts
            ├── +page.svelte
            ├── examples/
               ├── +layout.svelte (100 tokens)
               ├── +page.svelte
               ├── a11y/
                  ├── +page.svelte (400 tokens)
               ├── add-node-on-drop/
                  ├── +page.svelte
                  ├── Flow.svelte (600 tokens)
               ├── color-mode/
                  ├── +page.svelte (300 tokens)
               ├── custom-connection-line/
                  ├── +page.svelte (200 tokens)
                  ├── ConnectionLine.svelte (100 tokens)
                  ├── CustomNode.svelte (100 tokens)
               ├── custom-minimap/
                  ├── +page.svelte (300 tokens)
                  ├── CustomMiniMapNode.svelte (100 tokens)
               ├── customnode/
                  ├── +page.svelte (300 tokens)
                  ├── CustomNode.svelte (200 tokens)
               ├── dagre/
                  ├── +page.svelte (400 tokens)
                  ├── nodes-and-edges.ts (300 tokens)
               ├── detached-handle/
                  ├── +page.svelte (100 tokens)
                  ├── CustomNode.svelte (100 tokens)
               ├── drag-n-drop/
                  ├── +page.svelte
                  ├── Flow.svelte (400 tokens)
                  ├── Sidebar.svelte (200 tokens)
               ├── edge-toolbar/
                  ├── +page.svelte (200 tokens)
                  ├── CustomEdge.svelte (100 tokens)
               ├── edges/
                  ├── +page.svelte (1000 tokens)
                  ├── ButtonEdge.svelte (300 tokens)
                  ├── CustomBezierEdge.svelte
               ├── figma/
                  ├── +page.svelte (300 tokens)
               ├── handle-connect/
                  ├── +page.svelte (300 tokens)
                  ├── MultiHandleNode.svelte (400 tokens)
                  ├── SingleHandleNode.svelte (200 tokens)
               ├── interaction/
                  ├── +page.svelte (1000 tokens)
               ├── intersections/
                  ├── +page.svelte
                  ├── +server.ts (200 tokens)
                  ├── Flow.svelte (300 tokens)
                  ├── nodes-and-edges.ts (100 tokens)
               ├── node-resizer/
                  ├── +page.svelte (800 tokens)
                  ├── BottomRightResizer.svelte (100 tokens)
                  ├── CustomResizer.svelte (300 tokens)
                  ├── DefaultResizer.svelte (200 tokens)
                  ├── HorizontalResizer.svelte (300 tokens)
                  ├── VerticalResizer.svelte (300 tokens)
                  ├── store.svelte.ts
                  ├── types.ts (100 tokens)
               ├── node-toolbar/
                  ├── +page.svelte (300 tokens)
                  ├── CustomNode.svelte (200 tokens)
                  ├── SelectedNodesToolbar.svelte (100 tokens)
               ├── overview/
                  ├── +page.svelte
                  ├── CustomEdge.svelte (200 tokens)
                  ├── CustomNode.svelte (200 tokens)
                  ├── CustomNodeDragHandle.svelte (100 tokens)
                  ├── Flow.svelte (1300 tokens)
                  ├── InitTracker.svelte (100 tokens)
               ├── reset/
                  ├── +page.svelte (100 tokens)
               ├── stress/
                  ├── +page.svelte (200 tokens)
               ├── subflows/
                  ├── +page.svelte (500 tokens)
                  ├── DebugNode.svelte (100 tokens)
               ├── two-way-viewport/
                  ├── +page.svelte
                  ├── Flow.svelte (200 tokens)
               ├── usenodesdata/
                  ├── +page.svelte (300 tokens)
                  ├── ResultNode.svelte (200 tokens)
                  ├── TextNode.svelte (100 tokens)
                  ├── UppercaseNode.svelte (200 tokens)
               ├── usesvelteflow/
                  ├── +page.svelte
                  ├── Flow.svelte (200 tokens)
                  ├── Sidebar.svelte (500 tokens)
               ├── useupdatenodeinternals/
                  ├── +page.svelte
                  ├── CustomNode.svelte (100 tokens)
                  ├── Flow.svelte (300 tokens)
               ├── validation/
                  ├── +page.svelte (200 tokens)
                  ├── style.css
            ├── tests/
               ├── generic/
                  ├── [topic]/
                     ├── [example]/
                        ├── +page.svelte (100 tokens)
                        ├── +page.ts (100 tokens)
                        ├── Flow.svelte (200 tokens)
      ├── static/
         ├── favicon.ico
      ├── svelte.config.js (200 tokens)
      ├── tsconfig.json (100 tokens)
      ├── vite.config.ts (100 tokens)
├── package.json (300 tokens)
├── packages/
   ├── react/
      ├── .env
      ├── .eslintrc.js
      ├── CHANGELOG.md (15.4k tokens)
      ├── README.md (1300 tokens)
      ├── package.json (500 tokens)
      ├── src/
         ├── additional-components/
            ├── Background/
               ├── Background.tsx (900 tokens)
               ├── Patterns.tsx (200 tokens)
               ├── index.tsx
               ├── types.ts (300 tokens)
            ├── Controls/
               ├── ControlButton.tsx (200 tokens)
               ├── Controls.tsx (900 tokens)
               ├── Icons/
                  ├── FitView.tsx (100 tokens)
                  ├── Lock.tsx (100 tokens)
                  ├── Minus.tsx
                  ├── Plus.tsx
                  ├── Unlock.tsx (100 tokens)
               ├── index.tsx
               ├── types.ts (400 tokens)
            ├── EdgeToolbar/
               ├── EdgeToolbar.tsx (400 tokens)
               ├── index.tsx
               ├── types.ts (100 tokens)
            ├── MiniMap/
               ├── MiniMap.tsx (1400 tokens)
               ├── MiniMapNode.tsx (200 tokens)
               ├── MiniMapNodes.tsx (800 tokens)
               ├── index.tsx
               ├── types.ts (800 tokens)
            ├── NodeResizer/
               ├── NodeResizeControl.tsx (1400 tokens)
               ├── NodeResizer.tsx (500 tokens)
               ├── index.tsx
               ├── types.ts (500 tokens)
            ├── NodeToolbar/
               ├── NodeToolbar.tsx (900 tokens)
               ├── NodeToolbarPortal.tsx (100 tokens)
               ├── index.tsx
               ├── types.ts (200 tokens)
            ├── index.ts
         ├── components/
            ├── A11yDescriptions/
               ├── index.tsx (300 tokens)
            ├── Attribution/
               ├── index.tsx (100 tokens)
            ├── BatchProvider/
               ├── index.tsx (700 tokens)
               ├── types.ts
               ├── useQueue.ts (400 tokens)
            ├── ConnectionLine/
               ├── index.tsx (700 tokens)
            ├── EdgeLabelRenderer/
               ├── index.tsx (400 tokens)
            ├── EdgeWrapper/
               ├── EdgeUpdateAnchors.tsx (900 tokens)
               ├── index.tsx (1700 tokens)
               ├── utils.ts (100 tokens)
            ├── Edges/
               ├── BaseEdge.tsx (400 tokens)
               ├── BezierEdge.tsx (500 tokens)
               ├── EdgeAnchor.tsx (300 tokens)
               ├── EdgeText.tsx (500 tokens)
               ├── SimpleBezierEdge.tsx (800 tokens)
               ├── SmoothStepEdge.tsx (500 tokens)
               ├── StepEdge.tsx (300 tokens)
               ├── StraightEdge.tsx (400 tokens)
               ├── index.ts (100 tokens)
            ├── Handle/
               ├── index.tsx (1700 tokens)
            ├── NodeWrapper/
               ├── index.tsx (1600 tokens)
               ├── useNodeObserver.ts (500 tokens)
               ├── utils.tsx (200 tokens)
            ├── Nodes/
               ├── DefaultNode.tsx (100 tokens)
               ├── GroupNode.tsx
               ├── InputNode.tsx (100 tokens)
               ├── OutputNode.tsx (100 tokens)
               ├── utils.ts (200 tokens)
            ├── NodesSelection/
               ├── index.tsx (600 tokens)
            ├── Panel/
               ├── index.tsx (300 tokens)
            ├── ReactFlowProvider/
               ├── index.tsx (800 tokens)
            ├── SelectionListener/
               ├── index.tsx (500 tokens)
            ├── StoreUpdater/
               ├── index.tsx (1100 tokens)
            ├── UserSelection/
               ├── index.tsx (200 tokens)
            ├── ViewportPortal/
               ├── index.tsx (200 tokens)
         ├── container/
            ├── EdgeRenderer/
               ├── MarkerDefinitions.tsx (500 tokens)
               ├── MarkerSymbols.tsx (300 tokens)
               ├── index.tsx (600 tokens)
            ├── FlowRenderer/
               ├── index.tsx (900 tokens)
            ├── GraphView/
               ├── index.tsx (1300 tokens)
               ├── useNodeOrEdgeTypesWarning.ts (200 tokens)
               ├── useStylesLoadedWarning.ts (100 tokens)
            ├── NodeRenderer/
               ├── index.tsx (900 tokens)
               ├── useResizeObserver.ts (200 tokens)
            ├── Pane/
               ├── index.tsx (1800 tokens)
            ├── ReactFlow/
               ├── Wrapper.tsx (300 tokens)
               ├── index.tsx (2.2k tokens)
               ├── init-values.ts
            ├── Viewport/
               ├── index.tsx (100 tokens)
            ├── ZoomPane/
               ├── index.tsx (900 tokens)
         ├── contexts/
            ├── NodeIdContext.ts (200 tokens)
            ├── StoreContext.ts
         ├── custom.d.ts (omitted)
         ├── hooks/
            ├── useColorModeClass.ts (200 tokens)
            ├── useConnection.ts (500 tokens)
            ├── useDrag.ts (300 tokens)
            ├── useEdges.ts (200 tokens)
            ├── useGlobalKeyHandler.ts (300 tokens)
            ├── useHandleConnections.ts (500 tokens)
            ├── useInternalNode.ts (200 tokens)
            ├── useIsomorphicLayoutEffect.ts
            ├── useKeyPress.ts (1400 tokens)
            ├── useMoveSelectedNodes.ts (400 tokens)
            ├── useNodeConnections.ts (500 tokens)
            ├── useNodes.ts (200 tokens)
            ├── useNodesData.ts (300 tokens)
            ├── useNodesEdgesState.ts (1000 tokens)
            ├── useNodesInitialized.ts (400 tokens)
            ├── useOnInitHandler.ts (100 tokens)
            ├── useOnSelectionChange.ts (400 tokens)
            ├── useOnViewportChange.ts (300 tokens)
            ├── useReactFlow.ts (2.2k tokens)
            ├── useResizeHandler.ts (300 tokens)
            ├── useStore.ts (600 tokens)
            ├── useUpdateNodeInternals.ts (500 tokens)
            ├── useViewport.ts (200 tokens)
            ├── useViewportHelper.ts (700 tokens)
            ├── useViewportSync.ts (100 tokens)
            ├── useVisibleEdgeIds.ts (300 tokens)
            ├── useVisibleNodeIds.ts (200 tokens)
         ├── index.ts (900 tokens)
         ├── store/
            ├── index.ts (2.5k tokens)
            ├── initialState.ts (700 tokens)
         ├── styles/
            ├── base.css
            ├── style.css (100 tokens)
            ├── utils.ts
         ├── types/
            ├── component-props.ts (5.2k tokens)
            ├── edges.ts (1800 tokens)
            ├── general.ts (1400 tokens)
            ├── index.ts
            ├── instance.ts (2000 tokens)
            ├── nodes.ts (800 tokens)
            ├── store.ts (1000 tokens)
         ├── utils/
            ├── changes.ts (1900 tokens)
            ├── general.ts (400 tokens)
            ├── index.ts
      ├── tsconfig.json
   ├── svelte/
      ├── .env
      ├── .gitignore
      ├── .npmrc
      ├── .prettierignore
      ├── .prettierrc
      ├── CHANGELOG.md (11.3k tokens)
      ├── README.md (1100 tokens)
      ├── eslint.config.js (200 tokens)
      ├── package.json (600 tokens)
      ├── src/
         ├── lib/
            ├── actions/
               ├── drag/
                  ├── index.ts (500 tokens)
               ├── portal/
                  ├── index.ts
                  ├── portal.svelte.ts (200 tokens)
                  ├── utils.svelte.ts (100 tokens)
               ├── zoom/
                  ├── index.ts (400 tokens)
            ├── components/
               ├── A11yDescriptions/
                  ├── A11yDescriptions.svelte (200 tokens)
                  ├── index.ts (100 tokens)
               ├── Attribution/
                  ├── Attribution.svelte (100 tokens)
                  ├── index.ts
                  ├── types.ts
               ├── ConnectionLine/
                  ├── ConnectionLine.svelte (400 tokens)
                  ├── index.ts
               ├── EdgeLabel/
                  ├── EdgeLabel.svelte (300 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── EdgeReconnectAnchor/
                  ├── EdgeReconnectAnchor.svelte (700 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── EdgeWrapper/
                  ├── EdgeWrapper.svelte (900 tokens)
                  ├── index.ts
               ├── Handle/
                  ├── Handle.svelte (1500 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── KeyHandler/
                  ├── KeyHandler.svelte (800 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── NodeSelection/
                  ├── NodeSelection.svelte (600 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── NodeWrapper/
                  ├── NodeWrapper.svelte (1900 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── Selection/
                  ├── Selection.svelte (100 tokens)
                  ├── index.ts
               ├── SvelteFlowProvider/
                  ├── SvelteFlowProvider.svelte (200 tokens)
                  ├── index.ts
                  ├── types.ts
               ├── ViewportPortal/
                  ├── ViewportPortal.svelte (100 tokens)
                  ├── index.ts
                  ├── types.ts
               ├── edges/
                  ├── BaseEdge.svelte (200 tokens)
                  ├── BezierEdge.svelte (200 tokens)
                  ├── BezierEdgeInternal.svelte (100 tokens)
                  ├── SmoothStepEdge.svelte (200 tokens)
                  ├── SmoothStepEdgeInternal.svelte (100 tokens)
                  ├── StepEdge.svelte (200 tokens)
                  ├── StepEdgeInternal.svelte (100 tokens)
                  ├── StraightEdge.svelte (100 tokens)
                  ├── StraightEdgeInternal.svelte (100 tokens)
                  ├── index.ts (200 tokens)
               ├── nodes/
                  ├── DefaultNode.svelte (100 tokens)
                  ├── GroupNode.svelte
                  ├── InputNode.svelte (100 tokens)
                  ├── OutputNode.svelte (100 tokens)
            ├── container/
               ├── EdgeRenderer/
                  ├── EdgeRenderer.svelte (200 tokens)
                  ├── MarkerDefinition/
                     ├── Marker.svelte (200 tokens)
                     ├── MarkerDefinition.svelte (100 tokens)
                     ├── index.ts
                  ├── index.ts
               ├── NodeRenderer/
                  ├── NodeRenderer.svelte (400 tokens)
                  ├── index.ts
               ├── Pane/
                  ├── Pane.svelte (1500 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── Panel/
                  ├── Panel.svelte (100 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── SvelteFlow/
                  ├── SvelteFlow.svelte (1200 tokens)
                  ├── Wrapper.svelte (800 tokens)
                  ├── index.ts
                  ├── types.ts (3.8k tokens)
               ├── Viewport/
                  ├── Viewport.svelte (100 tokens)
                  ├── index.ts
               ├── Zoom/
                  ├── Zoom.svelte (500 tokens)
                  ├── index.ts
                  ├── types.ts (200 tokens)
            ├── hooks/
               ├── derivedWarning.svelte.ts (200 tokens)
               ├── useColorMode.svelte.ts (100 tokens)
               ├── useConnection.svelte.ts (100 tokens)
               ├── useInitialized.svelte.ts (100 tokens)
               ├── useInternalNode.svelte.ts (100 tokens)
               ├── useNodeConnections.svelte.ts (600 tokens)
               ├── useNodesData.svelte.ts (300 tokens)
               ├── useNodesEdgesViewport.svelte.ts (300 tokens)
               ├── useOnSelectionChange.svelte.ts (100 tokens)
               ├── useStore.ts (100 tokens)
               ├── useSvelteFlow.svelte.ts (3.7k tokens)
               ├── useUpdateNodeInternals.svelte.ts (300 tokens)
            ├── index.ts (700 tokens)
            ├── plugins/
               ├── Background/
                  ├── Background.svelte (400 tokens)
                  ├── DotPattern.svelte (100 tokens)
                  ├── LinePattern.svelte (100 tokens)
                  ├── index.ts
                  ├── types.ts (300 tokens)
               ├── Controls/
                  ├── ControlButton.svelte (100 tokens)
                  ├── Controls.svelte (700 tokens)
                  ├── Icons/
                     ├── Fit.svelte (100 tokens)
                     ├── Lock.svelte (100 tokens)
                     ├── Minus.svelte
                     ├── Plus.svelte
                     ├── Unlock.svelte (100 tokens)
                  ├── index.ts
                  ├── types.ts (200 tokens)
               ├── EdgeToolbar/
                  ├── EdgeToolbar.svelte (200 tokens)
                  ├── index.ts
                  ├── types.ts (100 tokens)
               ├── Minimap/
                  ├── Minimap.svelte (1000 tokens)
                  ├── MinimapNode.svelte (300 tokens)
                  ├── index.ts
                  ├── interactive.ts (300 tokens)
                  ├── types.ts (500 tokens)
               ├── NodeResizer/
                  ├── NodeResizer.svelte (200 tokens)
                  ├── ResizeControl.svelte (800 tokens)
                  ├── index.ts
                  ├── types.ts (400 tokens)
               ├── NodeToolbar/
                  ├── NodeToolbar.svelte (500 tokens)
                  ├── index.ts
                  ├── types.ts (200 tokens)
            ├── store/
               ├── index.ts (2.2k tokens)
               ├── initial-store.svelte.ts (3.4k tokens)
               ├── types.ts (800 tokens)
               ├── visibleElements.ts (800 tokens)
            ├── types/
               ├── edges.ts (1000 tokens)
               ├── events.ts (700 tokens)
               ├── general.ts (400 tokens)
               ├── index.ts
               ├── nodes.ts (400 tokens)
            ├── utils/
               ├── index.ts (300 tokens)
         ├── routes/
            ├── +page.svelte
            ├── +page.svelte.d.ts (omitted)
         ├── styles/
            ├── base.css (100 tokens)
            ├── style.css (100 tokens)
      ├── svelte.config.js (100 tokens)
      ├── tsconfig.json (100 tokens)
   ├── system/
      ├── .eslintrc.js
      ├── CHANGELOG.md (5.4k tokens)
      ├── README.md (500 tokens)
      ├── package.json (400 tokens)
      ├── src/
         ├── constants.ts (700 tokens)
         ├── index.ts
         ├── styles/
            ├── base.css (200 tokens)
            ├── init.css (1800 tokens)
            ├── node-resizer.css (400 tokens)
            ├── style.css (1000 tokens)
         ├── types/
            ├── changes.ts (500 tokens)
            ├── edges.ts (800 tokens)
            ├── general.ts (2.3k tokens)
            ├── handles.ts (300 tokens)
            ├── index.ts
            ├── nodes.ts (1000 tokens)
            ├── panzoom.ts (400 tokens)
            ├── utils.ts (300 tokens)
         ├── utils/
            ├── connections.ts (200 tokens)
            ├── dom.ts (700 tokens)
            ├── edge-toolbar.ts (100 tokens)
            ├── edges/
               ├── bezier-edge.ts (900 tokens)
               ├── general.ts (1200 tokens)
               ├── index.ts
               ├── positions.ts (800 tokens)
               ├── smoothstep-edge.ts (2.1k tokens)
               ├── straight-edge.ts (400 tokens)
            ├── general.ts (2.7k tokens)
            ├── graph.ts (3.1k tokens)
            ├── index.ts (100 tokens)
            ├── marker.ts (300 tokens)
            ├── node-toolbar.ts (300 tokens)
            ├── shallow-node-data.ts (100 tokens)
            ├── store.ts (3.7k tokens)
            ├── types.ts
         ├── xydrag/
            ├── XYDrag.ts (2.5k tokens)
            ├── index.ts
            ├── utils.ts (800 tokens)
         ├── xyhandle/
            ├── XYHandle.ts (1800 tokens)
            ├── index.ts
            ├── types.ts (400 tokens)
            ├── utils.ts (800 tokens)
         ├── xyminimap/
            ├── index.ts (700 tokens)
         ├── xypanzoom/
            ├── XYPanZoom.ts (1700 tokens)
            ├── eventhandler.ts (1500 tokens)
            ├── filter.ts (600 tokens)
            ├── index.ts
            ├── utils.ts (300 tokens)
         ├── xyresizer/
            ├── XYResizer.ts (2.1k tokens)
            ├── index.ts
            ├── types.ts (300 tokens)
            ├── utils.ts (2.2k tokens)
      ├── tsconfig.json (100 tokens)
├── pnpm-lock.yaml (omitted)
├── pnpm-workspace.yaml
├── tests/
   ├── playwright/
      ├── .gitignore
      ├── README.md (500 tokens)
      ├── e2e/
         ├── constants.ts
         ├── edges.spec.ts (1200 tokens)
         ├── node-toolbar.spec.ts (700 tokens)
         ├── nodes.spec.ts (2.2k tokens)
         ├── pane.spec.ts (1200 tokens)
         ├── props.spec.ts (200 tokens)
         ├── utils.ts (100 tokens)
      ├── package-lock.json (15.5k tokens)
      ├── package.json (200 tokens)
      ├── playwright.react.config.ts
      ├── playwright.shared.config.ts (400 tokens)
      ├── playwright.svelte.config.ts
├── tooling/
   ├── eslint-config/
      ├── CHANGELOG.md (100 tokens)
      ├── package.json (100 tokens)
      ├── src/
         ├── index.js (200 tokens)
   ├── postcss-config/
      ├── postcss.config.js (100 tokens)
   ├── rollup-config/
      ├── package.json (100 tokens)
      ├── src/
         ├── index.js (400 tokens)
   ├── tsconfig/
      ├── base.json (100 tokens)
      ├── package.json
      ├── react.json
├── turbo.json (100 tokens)
```


## /.changeset/README.md

# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)


## /.changeset/config.json

```json path="/.changeset/config.json" 
{
  "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
  "changelog": ["@changesets/changelog-github", { "repo": "xyflow/xyflow" }],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": ["react-examples", "svelte-examples", "astro-examples", "playwright"]
}

```

## /.changeset/sharp-toys-cheer.md

---
'@xyflow/react': minor
'@xyflow/svelte': minor
'@xyflow/system': patch
---

Pass current pointer position to connection


## /.coderabbit.yaml

```yaml path="/.coderabbit.yaml" 
# Set the language for reviews by using the corresponding ISO language code.
language: 'en-US'

# Set the tone of reviews and chat. Example: 'You must use talk like Mr. T. I pity the fool who doesn't!'
tone_instructions: ''

# Enable early-access features.
early_access: false

# Enable free tier features for users not on a paid plan.
enable_free_tier: true

# =============================================================================
# REVIEWS
# Settings related to reviews.
# =============================================================================

# Settings related to reviews.
# Default: {}
reviews:
  # Set the profile for reviews. Assertive profile yields more feedback, that may be considered nitpicky.
  # Options: chill, assertive
  # Default: "chill"
  profile: 'chill'

  # Approve the review once CodeRabbit’s comments are resolved and no pre-merge checks are in an error state. Note: In GitLab, all discussions must be resolved.
  # Default: false
  request_changes_workflow: false

  # Generate a high level summary of the changes in the PR/MR description.
  # Default: true
  high_level_summary: false

  # Placeholder in the PR/MR description that gets replaced with the high level summary.
  # Default: "@coderabbitai summary"
  high_level_summary_placeholder: '@coderabbitai summary'

  # Include the high level summary in the walkthrough comment.
  # Default: false
  high_level_summary_in_walkthrough: false

  # Add this keyword in the PR/MR title to auto-generate the title.
  # Default: "@coderabbitai"
  auto_title_placeholder: '@coderabbitai'

  # Auto Title Instructions - Custom instructions for auto-generating the PR/MR title.
  # Default: ""
  auto_title_instructions: ''

  # Post review details on each review. Additionally, post a review status when a review is skipped in certain cases.
  # Default: true
  review_status: false

  # Set the commit status to 'pending' when the review is in progress and 'success' when it is complete.
  # Default: true
  commit_status: false

  # Set the commit status to 'failure' when the PR cannot be reviewed by CodeRabbit for any reason.
  # Default: false
  fail_commit_status: false

  # Generate walkthrough in a markdown collapsible section.
  # Default: false
  collapse_walkthrough: false

  # Generate a summary of the changed files in the walkthrough.
  # Default: true
  changed_files_summary: false

  # Generate sequence diagrams in the walkthrough.
  # Default: true
  sequence_diagrams: false

  # Estimate the code review effort in the walkthrough.
  # Default: true
  estimate_code_review_effort: false

  # Generate an assessment of how well the changes address the linked issues in the walkthrough.
  # Default: true
  assess_linked_issues: false

  # Include possibly related issues in the walkthrough.
  # Default: true
  related_issues: false

  # Related PRs - Include possibly related pull requests in the walkthrough.
  # Default: true
  related_prs: false

  # Suggest labels based on the changes in the pull request in the walkthrough.
  # Default: true
  suggested_labels: false

  # Labeling Instructions - Provide guidelines for suggesting labels for the PR/MR. When specific labels or instructions are provided, only those labels are considered, though previous examples are still used to inform the suggestions. If no such labels are provided, suggestions are based solely on previous PR/MRs.
  # Default: []
  labeling_instructions: []

  # Automatically apply the suggested labels to the PR/MR.
  # Default: false
  auto_apply_labels: false

  # Suggest reviewers based on the changes in the pull request in the walkthrough.
  # Default: true
  suggested_reviewers: false

  # Automatically assign suggested reviewers to the pull request
  # Default: false
  auto_assign_reviewers: false

  # Post an in-progress fortune message while the review is in progress.
  # Default: true
  in_progress_fortune: false

  # Generate a poem in the walkthrough comment.
  # Default: true
  poem: false

  # Specify file patterns to include or exclude in a review using glob patterns (e.g., !dist/**, src/**). These patterns also apply to 'git sparse-checkout', including specified patterns and ignoring excluded ones (starting with '!') when cloning the repository.
  # Default: []
  path_filters: []

  # Path Instructions - Provide specific additional guidelines for code review based on file paths.
  # Default: []
  path_instructions: []

  # Abort the in-progress review if the pull request is closed or merged.
  # Default: true
  abort_on_close: true

  # Disable caching of code and dependencies. This will force CodeRabbit to download the code and dependencies fresh from the repository each time.
  # Default: false
  disable_cache: false

  # Configuration for auto review
  # Default: {}
  auto_review:
    # Automatic Review - Automatic code review
    # Default: true
    enabled: true

    # Automatic Incremental Review - Automatic incremental code review on each push
    # Default: true
    auto_incremental_review: true

    # Ignore reviewing if the title of the pull request contains any of these keywords (case-insensitive).
    # Default: []
    ignore_title_keywords: []

    # List of labels to control which PRs/MRs to review. Labels starting with '!' are negative matches. Examples: ['bug', 'feature'] - reviews PRs with 'bug' OR 'feature' label. ['!wip'] - reviews all PRs except those with 'wip' label. ['bug', '!wip'] - reviews PRs with 'bug' label but not if they have 'wip' label.
    # Default: []
    labels: []

    # Review draft PRs/MRs.
    # Default: false
    drafts: false

    # Base branches (other than the default branch) to review. Accepts regex patterns. Use '.*' to match all branches.
    # Default: []
    base_branches: []

    # Ignore reviewing pull requests by these usernames. These should match the Git platform usernames exactly, not the email addresses.
    # Default: []
    ignore_usernames: []

  # Configuration for finishing touches
  # Default: {}
  finishing_touches:
    # Docstrings - Options for generating Docstrings for your PRs/MRs.
    # Default: {}
    docstrings:
      # Docstrings - Allow CodeRabbit to generate docstrings for PRs/MRs.
      # Default: true
      enabled: false

    # Unit Tests - Options for generating unit tests for your PRs/MRs.
    # Default: {}
    unit_tests:
      # Unit Tests - Allow CodeRabbit to generate unit tests for PRs/MRs.
      # Default: true
      enabled: false

  # Configuration for pre merge checks
  # Default: {}
  pre_merge_checks:
    # Docstring Coverage - Checks if the code has sufficient docstrings.
    # Default: {}
    docstrings:
      # Mode - Determines how strictly the docstring coverage check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved.
      # Options: off, warning, error
      # Default: "warning"
      mode: 'warning'

      # Percentage threshold for docstring coverage check.
      # Default: 80
      threshold: 80

    # Title Check - Checks if the pull request title is appropriate and follows best practices.
    # Default: {}
    title:
      # Mode - Determines how strictly the title check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved.
      # Options: off, warning, error
      # Default: "warning"
      mode: 'warning'

      # Requirements - Requirements for the pull request title. Example: 'Title should be concise and descriptive, ideally under 50 characters.'
      # Default: ""
      requirements: ''

    # Description Check - Checks if the pull request description is appropriate and follows best practices.
    # Default: {}
    description:
      # Mode - Determines how strictly the description check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved.
      # Options: off, warning, error
      # Default: "warning"
      mode: 'warning'

    # Linked Issue Assessment - Checks if the pull request addresses the linked issues. Generate an assessment of how well the changes address the linked issues.
    # Default: {}
    issue_assessment:
      # Mode - Determines how strictly the issue assessment check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved.
      # Options: off, warning, error
      # Default: "warning"
      mode: 'warning'

    # Custom Pre-merge Checks - Add unique checks to enforce your team's standards before merging a pull request. Each check must have a unique name (up to 50 characters) and clear instructions (up to 10000 characters). Use these to automatically verify coding, security, documentation, or business rules and maintain code quality.
    # Default: []
    custom_checks: []

  # Tools that provide additional context to code reviews.
  # Default: {}
  tools:
    # Enable ast-grep - ast-grep is a code analysis tool that helps you to find patterns in your codebase using abstract syntax trees patterns. - v0.39.6
    # Default: {}
    ast-grep:
      # List of rules directories.
      # Default: []
      rule_dirs: []

      # List of utils directories.
      # Default: []
      util_dirs: []

      # Use ast-grep essentials package.
      # Default: true
      essential_rules: true

      # Predefined packages to be used.
      # Default: []
      packages: []

    # ShellCheck is a static analysis tool that finds bugs in your shell scripts.
    # Default: {}
    shellcheck:
      # Enable ShellCheck - ShellCheck is a static analysis tool that finds bugs in your shell. - Enable ShellCheck integration. - v0.11.0
      # Default: true
      enabled: false

    # Ruff is a Python linter and code formatter.
    # Default: {}
    ruff:
      # Enable Ruff - Ruff is a Python linter and code formatter. - Enable Ruff integration. - v0.14.1
      # Default: true
      enabled: false

    # markdownlint-cli2 is a static analysis tool to enforce standards and consistency for Markdown files.
    # Default: {}
    markdownlint:
      # Enable markdownlint - markdownlint-cli2 is a static analysis tool to enforce standards and consistency for Markdown files. - Enable markdownlint integration. - v0.18.1
      # Default: true
      enabled: true

    # GitHub Checks integration configuration.
    # Default: {}
    github-checks:
      # Enable GitHub Checks - Enable integration, defaults to true - Enable GitHub Checks integration.
      # Default: true
      enabled: true

      # Time in milliseconds to wait for all GitHub Checks to conclude. Default 90 seconds, max 15 minutes (900000ms).
      # Default: 90000
      timeout_ms: 90000

    # LanguageTool is a style and grammar checker for 30+ languages.
    # Default: {}
    languagetool:
      # Enable LanguageTool - Enable LanguageTool integration.
      # Default: true
      enabled: true

      # IDs of rules to be enabled. The rule won't run unless 'level' is set to a level that activates the rule.
      # Default: []
      enabled_rules: []

      # IDs of rules to be disabled. Note: EN_UNPAIRED_BRACKETS, and EN_UNPAIRED_QUOTES are always disabled.
      # Default: []
      disabled_rules: []

      # IDs of categories to be enabled.
      # Default: []
      enabled_categories: []

      # IDs of categories to be disabled. Note: TYPOS, TYPOGRAPHY, and CASING are always disabled.
      # Default: []
      disabled_categories: []

      # Only the rules and categories whose IDs are specified with 'enabledRules' or 'enabledCategories' are enabled.
      # Default: false
      enabled_only: false

      # If set to 'picky', additional rules will be activated, i.e. rules that you might only find useful when checking formal text.
      # Options: default, picky
      # Default: "default"
      level: 'default'

    # Biome is a fast formatter, linter, and analyzer for web projects.
    # Default: {}
    biome:
      # Enable Biome - Biome is a fast formatter, linter, and analyzer for web projects. - Enable Biome integration. - v2.1.2
      # Default: true
      enabled: true

    # Hadolint is a Dockerfile linter.
    # Default: {}
    hadolint:
      # Enable Hadolint - Hadolint is a Dockerfile linter. - Enable Hadolint integration. - v2.14.0
      # Default: true
      enabled: false

    # SwiftLint integration configuration object.
    # Default: {}
    swiftlint:
      # Enable SwiftLint - SwiftLint is a Swift linter. - Enable SwiftLint integration. - v0.57.0
      # Default: true
      enabled: false

      # Optional path to the SwiftLint configuration file relative to the repository. This is useful when the configuration file is named differently than the default '.swiftlint.yml' or '.swiftlint.yaml'.
      config_file: 'example-value'

    # PHPStan is a tool to analyze PHP code.
    # Default: {}
    phpstan:
      # Enable PHPStan - PHPStan requires [config file](https://phpstan.org/config-reference#config-file) in your repository root. Please ensure that this file contains the `paths:` parameter. - v2.1.31
      # Default: true
      enabled: false

      # Level - Specify the [rule level](https://phpstan.org/user-guide/rule-levels) to run. This setting is ignored if your configuration file already has a `level:` parameter.
      # Options: default, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, max
      # Default: "default"
      level: 'default'

    # PHPMD is a tool to find potential problems in PHP code.
    # Default: {}
    phpmd:
      # Enable PHPMD - PHPMD is a tool to find potential problems in PHP code. - v2.15.0
      # Default: true
      enabled: false

    # PHP CodeSniffer is a PHP linter and coding standard checker.
    # Default: {}
    phpcs:
      # Enable PHP CodeSniffer - PHP CodeSniffer is a PHP linter and coding standard checker. - v3.7.2
      # Default: true
      enabled: false

    # golangci-lint is a fast linters runner for Go.
    # Default: {}
    golangci-lint:
      # Enable golangci-lint - golangci-lint is a fast linters runner for Go. - Enable golangci-lint integration. - v2.5.0
      # Default: true
      enabled: false

      # Optional path to the golangci-lint configuration file relative to the repository. Useful when the configuration file is named differently than the default '.golangci.yml', '.golangci.yaml', '.golangci.toml', '.golangci.json'.
      config_file: 'example-value'

    # YAMLlint is a linter for YAML files.
    # Default: {}
    yamllint:
      # Enable YAMLlint - YAMLlint is a linter for YAML files. - Enable YAMLlint integration. - v1.37.1
      # Default: true
      enabled: true

    # Gitleaks is a secret scanner.
    # Default: {}
    gitleaks:
      # Enable Gitleaks - Gitleaks is a secret scanner. - Enable Gitleaks integration. - v8.28.0
      # Default: true
      enabled: true

    # Checkov is a static code analysis tool for infrastructure-as-code files.
    # Default: {}
    checkov:
      # Enable Checkov - Checkov is a static code analysis tool for infrastructure-as-code files. - v3.2.334
      # Default: true
      enabled: false

    # Detekt is a static code analysis tool for Kotlin files.
    # Default: {}
    detekt:
      # Enable detekt - detekt is a static code analysis tool for Kotlin files. - v1.23.8
      # Default: true
      enabled: false

      # Optional path to the detekt configuration file relative to the repository.
      config_file: 'example-value'

    # ESLint is a static code analysis tool for JavaScript files.
    # Default: {}
    eslint:
      # Enable ESLint - ESLint is a static code analysis tool for JavaScript files.
      # Default: true
      enabled: true

    # Flake8 is a Python linter that wraps PyFlakes, pycodestyle and Ned Batchelder's McCabe script.
    # Default: {}
    flake8:
      # Enable Flake8 - Flake8 is a Python linter that wraps PyFlakes, pycodestyle and Ned Batchelder's McCabe script. - v7.3.0
      # Default: true
      enabled: false

    # RuboCop is a Ruby static code analyzer (a.k.a. linter ) and code formatter.
    # Default: {}
    rubocop:
      # Enable RuboCop - RuboCop is a Ruby static code analyzer (a.k.a. linter ) and code formatter. - v1.81.1
      # Default: true
      enabled: false

    # Buf offers linting for Protobuf files.
    # Default: {}
    buf:
      # Enable Buf - Buf offers linting for Protobuf files. - v1.58.0
      # Default: true
      enabled: false

    # Regal is a linter and language server for Rego.
    # Default: {}
    regal:
      # Enable Regal - Regal is a linter and language server for Rego. - v0.36.1
      # Default: true
      enabled: false

    # actionlint is a static checker for GitHub Actions workflow files.
    # Default: {}
    actionlint:
      # Enable actionlint - is a static checker for GitHub Actions workflow files. - v1.7.8
      # Default: true
      enabled: false

    # PMD is an extensible multilanguage static code analyzer. It’s mainly concerned with Java.
    # Default: {}
    pmd:
      # Enable PMD - PMD is an extensible multilanguage static code analyzer. It’s mainly concerned with Java. - v7.17.0
      # Default: true
      enabled: false

      # Optional path to the PMD configuration file relative to the repository.
      config_file: 'example-value'

    # Configuration for Clang to perform static analysis on C and C++ code
    # Default: {}
    clang:
      # Enable Clang for C/C++ static analysis and code quality checks - v14.0.6
      # Default: true
      enabled: false

    # Cppcheck is a static code analysis tool for the C and C++ programming languages.
    # Default: {}
    cppcheck:
      # Enable Cppcheck - Cppcheck is a static code analysis tool for the C and C++ programming languages. - v2.18.0
      # Default: true
      enabled: false

    # Semgrep is a static analysis tool designed to scan code for security vulnerabilities and code quality issues.
    # Default: {}
    semgrep:
      # Enable Semgrep - Semgrep is a static analysis tool designed to scan code for security vulnerabilities and code quality issues. - Enable Semgrep integration. - v1.140.0
      # Default: true
      enabled: false

      # Optional path to the Semgrep configuration file relative to the repository.
      config_file: 'example-value'

    # CircleCI tool is a static checker for CircleCI config files.
    # Default: {}
    circleci:
      # Enable CircleCI - CircleCI tool is a static checker for CircleCI config files. - v0.1.33494
      # Default: true
      enabled: false

    # Clippy is a collection of lints to catch common mistakes and improve your Rust code.
    # Default: {}
    clippy:
      # Enable Clippy - Clippy is a collection of lints to catch common mistakes and improve your Rust code. - Enable Clippy integration.
      # Default: true
      enabled: false

    # SQLFluff is an open source, dialect-flexible and configurable SQL linter.
    # Default: {}
    sqlfluff:
      # Enable SQLFluff - SQLFluff is an open source, dialect-flexible and configurable SQL linter. - v3.5.0
      # Default: true
      enabled: false

    # Configuration for Prisma Schema linting to ensure schema file quality
    # Default: {}
    prismaLint:
      # Enable Prisma Schema linting - Prisma Schema linting helps maintain consistent and error-free schema files - v0.10.3
      # Default: true
      enabled: false

    # Pylint is a Python static code analysis tool.
    # Default: {}
    pylint:
      # Enable Pylint - Pylint is a Python static code analysis tool. - v4.0.1
      # Default: true
      enabled: false

    # Oxlint is a JavaScript/TypeScript linter for OXC written in Rust.
    # Default: {}
    oxc:
      # Enable Oxlint - Oxlint is a JavaScript/TypeScript linter for OXC written in Rust. - v1.23.0
      # Default: true
      enabled: false

    # Configuration for Shopify Theme Check to ensure theme quality and best practices
    # Default: {}
    shopifyThemeCheck:
      # Enable Shopify Theme Check - A linter for Shopify themes that helps you follow Shopify theme & Liquid best practices - cli 3.84.2 - theme 3.58.2
      # Default: true
      enabled: false

    # Configuration for Lua code linting to ensure code quality
    # Default: {}
    luacheck:
      # Enable Lua code linting - Luacheck helps maintain consistent and error-free Lua code - v1.2.0
      # Default: true
      enabled: false

    # Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications. - v7.1.0
    # Default: {}
    brakeman:
      # Enable Brakeman - Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications. - v7.1.0
      # Default: true
      enabled: false

    # dotenv-linter is a tool for checking and fixing .env files for problems and best practices
    # Default: {}
    dotenvLint:
      # Enable dotenv-linter - dotenv-linter is a tool for checking and fixing .env files for problems and best practices - v4.0.0
      # Default: true
      enabled: false

    # HTMLHint is a static code analysis tool for HTML files.
    # Default: {}
    htmlhint:
      # Enable HTMLHint - HTMLHint is a static code analysis tool for HTML files. - Enable HTMLHint integration. - v1.7.1
      # Default: true
      enabled: false

    # checkmake is a linter for Makefiles.
    # Default: {}
    checkmake:
      # Enable checkmake - checkmake is a linter for Makefiles. - v0.2.2
      # Default: true
      enabled: false

    # OSV Scanner is a tool for vulnerability package scanning.
    # Default: {}
    osvScanner:
      # Enable OSV Scanner - OSV Scanner is a tool for vulnerability package scanning - v2.2.3
      # Default: true
      enabled: false

# =============================================================================
# CHAT
# Configuration for chat
# =============================================================================

# Configuration for chat
# Default: {}
chat:
  # Generate art in response to chat messages. CodeRabbit expresses emotions as either ASCII or Emoji art.
  # Default: true
  art: true

  # Enable the bot to reply automatically without requiring the user to tag it.
  # Default: true
  auto_reply: true

  # Configuration for integrations
  # Default: {}
  integrations:
    # Configuration for jira
    # Default: {}
    jira:
      # Jira - Enable the Jira integration for opening issues, etc. 'auto' disables the integration for public repositories.
      # Options: auto, enabled, disabled
      # Default: "auto"
      usage: 'disabled'

    # Configuration for linear
    # Default: {}
    linear:
      # Linear - Enable the Linear integration for opening issues, etc. 'auto' disables the integration for public repositories.
      # Options: auto, enabled, disabled
      # Default: "auto"
      usage: 'disabled'

# =============================================================================
# KNOWLEDGE BASE
# Configuration for knowledge base
# =============================================================================

# Configuration for knowledge base
# Default: {}
knowledge_base:
  # Opt Out - Disable all knowledge base features that require data retention. If you opt out after opting in, all of your existing knowledge base data will be removed from the system.
  # Default: false
  opt_out: false

  # Configuration for web search
  # Default: {}
  web_search:
    # Web Search - Enable the web search integration.
    # Default: true
    enabled: true

  # CodeRabbit will analyse and learn from your organization's code guidelines, which you can mention in the file patterns section. These guidelines will then be used to conduct thorough code reviews.
  # Default: {}
  code_guidelines:
    # Enabled - Enable CodeRabbit to enforce your organization's coding standards during reviews.
    # Default: true
    enabled: true

    # File Patterns - Specify files for your coding guideline documents in this section. CodeRabbit will scan these files to understand your team's standards and apply them during code reviews. Multiple files supported. File names are case-sensitive. Common files like: (**/.cursorrules, .github/copilot-instructions.md, .github/instructions/*.instructions.md, **/CLAUDE.md, **/GEMINI.md, **/.cursor/rules/*, **/.windsurfrules, **/.clinerules/*, **/.rules/*, **/AGENT.md, **/AGENTS.md) are included by default.
    # Default: []
    filePatterns: []

  # Configuration for learnings
  # Default: {}
  learnings:
    # Learnings - Specify the scope of learnings to use for the knowledge base. 'local' uses the repository's learnings, 'global' uses the organization's learnings, and 'auto' uses repository's learnings for public repositories and organization's learnings for private repositories.
    # Options: local, global, auto
    # Default: "auto"
    scope: 'auto'

  # Configuration for issues
  # Default: {}
  issues:
    # Issues - Specify the scope of git platform (GitHub/GitLab) issues to use for the knowledge base. 'local' uses the repository's issues, 'global' uses the organization's issues, and 'auto' uses repository's issues for public repositories and organization's issues for private repositories.
    # Options: local, global, auto
    # Default: "auto"
    scope: 'auto'

  # Configuration for jira
  # Default: {}
  jira:
    # Jira - Enable the Jira knowledge base integration. 'auto' disables the integration for public repositories.
    # Options: auto, enabled, disabled
    # Default: "auto"
    usage: 'disabled'

    # Jira Project Keys - Specify the Jira project keys to use for the knowledge base.
    # Default: []
    project_keys: []

  # Configuration for linear
  # Default: {}
  linear:
    # Linear - Enable the Linear knowledge base integration. 'auto' disables the integration for public repositories.
    # Options: auto, enabled, disabled
    # Default: "auto"
    usage: 'disabled'

    # Linear Team Keys - Specify the Linear team keys (identifiers) to use for the knowledge base. E.g. 'ENG'
    # Default: []
    team_keys: []

  # Configuration for pull requests
  # Default: {}
  pull_requests:
    # Pull Requests - Specify the scope of pull requests to use for the knowledge base. 'local' uses the repository's pull requests, 'global' uses the organization's pull requests, and 'auto' uses repository's pull requests for public repositories and organization's pull requests for private repositories.
    # Options: local, global, auto
    # Default: "auto"
    scope: 'auto'

  # Configuration for mcp
  # Default: {}
  mcp:
    # MCP - Enable the MCP knowledge base integration. 'auto' disables the integration for public repositories.
    # Options: auto, enabled, disabled
    # Default: "auto"
    usage: 'auto'

    # MCP Disabled Servers - Specify MCP server labels to disable (case-insensitive). These servers will be excluded from reviews and knowledge base queries.
    # Default: []
    disabled_servers: []

```

## /.github/FUNDING.yml

```yml path="/.github/FUNDING.yml" 
# These are supported funding model platforms

github: [xyflow]
open_collective: xyflow

```

## /.github/ISSUE_TEMPLATE/bug_report.yml

```yml path="/.github/ISSUE_TEMPLATE/bug_report.yml" 
name: '🐛 Bug Report'
description: Create a report to help us improve React Flow
body:
  - type: markdown
    attributes:
      value: |
        Hello! :sparkles: You found the place to submit a bug or issue that you discovered in the xyflow library, thank you for helping us out!

        Having trouble making something work in xyflow, but it’s not a bug with the library itself? You’ll get the fastest response by asking in [github discussions](https://github.com/xyflow/xyflow/discussions) or our [discord server](https://discord.gg/RVmnytFmGW).

        **Search our [discussions](https://github.com/xyflow/xyflow/discussions?discussions_q=) and [issues](https://github.com/xyflow/xyflow/issues?q=)** to see if your problem has already been reported and you can add more context, or see if someone else has already solved your issue. And of course, check our [docs](https://reactflow.dev/learn) if you haven’t already.

        **Issue with the xyflow Docs?** Submit an issue in our [web repo](https://github.com/xyflow/web/issues) instead.

        With that out of the way, let’s get started :bug:
  - type: textarea
    id: platform
    attributes:
      label: What platform were you using when you found the bug?
      description: We only address issues for the most recent xyflow major release or upcoming releases.
      value: |
        - React Flow / Svelte Flow version: 
        - Browser and version: 
        - OS and version:
    validations:
      required: true
  - type: input
    id: link
    attributes:
      label: Live code example
      description: |
        This is the most important part of the bug report! 

        React Flow codesandbox starters
        - js: https://new.reactflow.dev/js
        - ts: https://new.reactflow.dev/ts

        Svelte Flow stackblitz starters
        - js: https://new.svelteflow.dev/js
        - ts: https://new.svelteflow.dev/ts 

        Avoid dependencies in your example for easier debugging.
      placeholder: https://stackblitz.com/edit/...  or Github Repo
    validations:
      required: false
  - type: textarea
    id: description
    attributes:
      label: Describe the Bug
      description: What exactly is happening when you encounter the bug?
    validations:
      required: true
  - type: textarea
    id: steps
    attributes:
      label: Steps to reproduce the bug or issue
      description: How can we see the reported behavior in the code example you provided? The more detail, the better!
      placeholder: |
        1. Go to '...'
        2. Click on '....'
        3. Scroll down to '....'
        4. See error
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: Expected behavior
      description: What would you expect to happen if there was no bug or issue?
      placeholder: As a user, I expected ___ behavior, but instead I am seeing ___
    validations:
      required: true
  - type: textarea
    id: screenshots_or_videos
    attributes:
      label: Screenshots or Videos
      description: |
        If applicable, add screenshots or a video to help explain your problem.
        Supported image/file types and file size limits- https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files
      placeholder: Drag your video or image files inside of this editor
  - type: textarea
    id: additional
    attributes:
      label: Additional context

```

## /.github/ISSUE_TEMPLATE/config.yml

```yml path="/.github/ISSUE_TEMPLATE/config.yml" 
blank_issues_enabled: false
contact_links:
  - name: 🤔 Feature Requests & Questions 
    url: https://github.com/wbkd/react-flow/discussions
    about: Please ask and answer questions in the discussions section

```

## /.github/ISSUE_TEMPLATE/feature_request.yml

```yml path="/.github/ISSUE_TEMPLATE/feature_request.yml" 
name: 🛠 Feature Request
description: Suggest a new feature or idea for React Flow
labels: feature request
body:
  - type: textarea
    id: description
    attributes:
      label: Please describe the feature that you want to propose
      description: A clear and concise description of what you want to happen.
    validations:
      required: true

```

## /.github/actions/ci-checks/action.yml

```yml path="/.github/actions/ci-checks/action.yml" 
name: 'CI checks'

runs:
  using: 'composite'
  steps:
    - name: Build
      run: pnpm build
      shell: bash
    - name: Typecheck
      run: pnpm typecheck
      shell: bash

```

## /.github/actions/ci-setup/action.yml

```yml path="/.github/actions/ci-setup/action.yml" 
name: 'CI setup'

runs:
  using: 'composite'
  steps:
    - name: Setup pnpm
      uses: pnpm/action-setup@v4
    - name: Setup node.js
      uses: actions/setup-node@v4
      with:
        node-version: 20.x
    - name: Install dependencies
      run: pnpm install
      shell: bash

```

## /.github/workflows/dispatchWebsiteUpdate.yaml

```yaml path="/.github/workflows/dispatchWebsiteUpdate.yaml" 
name: Dispatch Website Update
run-name: Dispatch Website Update for ${{ github.event.release.tag_name }}

on:
  release:
    types: [published, edited]

jobs:
  dispatch_website_update:
    if: ${{ contains(github.event.release.tag_name, 'svelte') || contains(github.event.release.tag_name, 'react') }}
    runs-on: ubuntu-latest
    steps:
      # Github Actions is not able to escape JSON strings with certain characters in them
      # which release bodies have plenty of. This step uses jq to extract the body and tag_name
      # and escape them so they can be used in the next step to create a JSON for the client-payload
      - name: Strip and escape JSON
        run: |
          {
            echo 'ESCAPED_PAYLOAD<<EOF'
            echo '${{ toJson(github.event.release) }}' | jq '{"body", "tag_name"}'
            echo EOF
          } >> "$GITHUB_ENV"
      - name: Dispatch Website Update
        uses: peter-evans/repository-dispatch@v3
        with:
          repository: xyflow/web
          token: ${{ secrets.PAT }}
          event-type: library-release
          client-payload: ${{ env.ESCAPED_PAYLOAD }}

```

## /.github/workflows/playwright.yml

```yml path="/.github/workflows/playwright.yml" 
name: Playwright Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  playwright:
    name: 'Playwright Tests'
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.51.1-jammy
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
      - uses: ./.github/actions/ci-setup
      - uses: ./.github/actions/ci-checks
      - name: Run your tests
        run: CI=true pnpm test:react
        env:
          HOME: /root

```

## /.github/workflows/release.yml

```yml path="/.github/workflows/release.yml" 
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
      - uses: ./.github/actions/ci-setup
      - uses: ./.github/actions/ci-checks

      - name: Create release PR or publish to npm
        id: changesets
        uses: xyflow/changeset-action@v1
        with:
          publish: pnpm release
          title: Release packages
          commit: 'chore(packages): bump'
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

```

## /.gitignore

```gitignore path="/.gitignore" 
.DS_Store
.cache
node_modules
examples/build
cypress/videos
cypress/screenshots
dist
stats.html
.eslintcache
.idea
.log
.turbo
.rollup.cache
```

## /.npmrc

```npmrc path="/.npmrc" 
legacy-peer-deps=true
strict-peer-dependencies=false
```

## /.prettierrc.json

```json path="/.prettierrc.json" 
{
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 120
}

```

## /CHANGELOG.md

# Changelog

Please check the [releases](https://github.com/wbkd/react-flow/releases) page for information about each release.

## /CODE_OF_CONDUCT.md

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective and/or restorative action in response to any instances of unacceptable behaviour.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Where it is appropriate and parties consent, project maintainers will endeavour to facilitate restorative justice over punitive action.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Additionally, we ask that all community members uphold the standards laid out in this Code of Conduct to create a safe and welcome space for all.

## Attribution

This Code of Conduct is a direct decendant of the [Gleam Code of Conduct](https://github.com/gleam-lang/gleam/blob/f793b5d28a3102276a8b861c7e16a19c5231426e/CODE_OF_CONDUCT.md), which is itself a decendant of the [Contributor Covenant (v1.4)](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html).


## /CONTRIBUTING.md

# Contributing to React Flow

Hello there! So glad you want to help out with React Flow and Svelte Flow 🤗 You’re the best. Here’s a guide for how you can contribute to the project.

# The new xyflow organization

Just recently we renamed our organization and repository of React Flow to "xyflow". Now you can not only find the source code of React Flow but also of Svelte Flow in this repository. We are in a state of transition. This is the current structure:

* React Flow `reactflow` version v11 is on branch v11
* Svelte Flow `@xyflow/svelte` can be found under [packages/svelte](./packages/svelte)
* React Flow v12 (not published yet) can be found under [packages/react](./packages/react)

# Our Contributing Philosophy

The direction of React Flow and Svelte Flow and which new features are added, and which are left out, is decided by the core team (sometimes referred to as a “cathedral” style of development). The core team is paid to do this work ([see how here](https://xyflow.com/blog/asking-for-money-for-open-source/)). With this model we ensure that the people doing the most work on the library are paid for their time and effort, and that we prevent the library from bloating.

That being said, our libraries are only interesting because of the people who make things with it, share their work, and discuss it. Some of the most important and undervalued work in open source is from non-code contributions, and that is where we can use the most help from you.

# How can I help?

The things we need the most help for the library and its community are:

**🐛 Bug reports:** We simply can’t catch them all. Check [existing issues](https://github.com/xyflow/xyflow/issues/) and discussion first, then [create a new issue](https://github.com/xyflow/xyflow/issues/new/choose) to tell us what’s up.

**💬 Answering questions** in our [Discord Server](https://discord.gg/Bqt6xrs) and [Github discussions](https://github.com/xyflow/xyflow/discussions).

🎬 **Create tutorials**. Send them to us and we’ll happily share them!

**✏️ Edit our [Docs](https://reactflow.dev/learn/concepts/introduction/)**: Make changes in our [web repo](https://github.com/xyflow/web), where our docs live.

All interactions should be done with care following our [Code of Conduct](https://github.com/xyflow/xyflow/blob/main/CODE_OF_CONDUCT.md).

## Enhancements

If you have an idea or suggestion for an enhancement to the React Flow or Svelte Flow library, please use the [New Features](https://github.com/xyflow/xyflow/discussions/categories/new-features) discussion section. If you want to build it yourself, **please reach out to us before you dive into a new pull request.** The direction of React Flow/ Svelte Flow and which new features are added are discussed in our Discord Server and in [this Github discussions section](https://github.com/xyflow/xyflow/discussions/categories/new-features), and in the end they are decided by the core team.

Talking to us first about the enhancement you want to build will be the most likely way to get your pull request into the library (see Our Contributing Philosophy above). We would hate to see you write code you’re proud of, just to learn that we’ve already been working on the same thing, or that we feel doesn’t fit into the core library.

### Contact us

To ask about a possible enhancement, email us at info@reactflow.dev


## 💫 Pull Requests

If you want to contribute improvements or new features we are happy to review your PR :)  
Please use a meaningful commit message and add a little description of your changes.

1. Install dependencies `pnpm install` 
2. Start dev server `pnpm dev` 
3. Test your changes with the existing examples or add a new one if it's needed for your changes
4. Run tests `pnpm test` and add new new tests if you are introducing a new feature

## Changeset Style Guide

*Inspired and taken from [Common Changelogs](https://github.com/vweevers/common-changelog?tab=readme-ov-file) and [Warp by Broad Institute](https://broadinstitute.github.io/warp/docs/contribution/contribute_to_warp/changelog_style/)*

If you are writing a changeset for a PR, here are some helpful tips:

## TLDR

- Changelogs are for humans
- Communicate the impact of changes
- Use active voice *and* presence tense *(”Fix …” instead of “… was fixed”)*
- Omit redundant verbs
    - *“Document” instead of “Add documentation”*
- Omit personal pronouns
- Use backticks for function or component names (`getNodesBounds`, `<ReactFlow />`, etc) for a better syntax highlighting

## Examples

**🛑 Bad:**
“minimap: use latest node dimensions”

**✅ Good:**
“Display minimap nodes even if onNodesChange is not implemented”

**🛑 Bad:**
“fix(handles): reconnect for connectionMode=loose”

**✅ Good:**
“Fix reconnections when connectionMode is set to loose” 

**🛑 Bad:**
“use correct index when using setNodes for inserting”

**✅ Good:**
“Fix incorrect order of nodes added with setNodes from useReactFlow”

## /README.md

![xyflow-header](https://user-images.githubusercontent.com/2857535/279643999-ffda9f91-6b6d-447d-82be-fcbd6103edb6.svg#gh-light-mode-only)
![xyflow-header-dark](https://user-images.githubusercontent.com/2857535/279644026-a01c231c-6c6e-4b41-96e0-a85c75c9acee.svg#gh-dark-mode-only)

<div align="center">

![GitHub License MIT](https://img.shields.io/github/license/wbkd/react-flow?color=%23ff0072)
![npm downloads](https://img.shields.io/npm/dt/reactflow?color=%23FF0072&label=React%20Flow%20downloads)
![npm downloads](https://img.shields.io/npm/dt/@xyflow/svelte?color=%23FF3E00&label=Svelte%20Flow%20downloads)

Powerful open source libraries for building node-based UIs with React or Svelte. Ready out-of-the-box and infinitely customizable.

[React Flow](https://reactflow.dev/) · [Svelte Flow](https://svelteflow.dev/) · [React Flow Pro](https://reactflow.dev/pro) · [Discord](https://discord.gg/Bqt6xrs)
</div>

---

## The xyflow mono repo

The xyflow repository is the home of four packages:
* React Flow 12 `@xyflow/react` [packages/react](./packages/react)
* React Flow 11 `reactflow` [v11 branch](https://github.com/xyflow/xyflow/tree/v11)
* Svelte Flow `@xyflow/svelte` [packages/svelte](./packages/svelte)
* Shared helper library `@xyflow/system` [packages/system](./packages/system)

## Commercial usage

**Are you using React Flow or Svelte Flow for a personal project?** Great! No sponsorship needed, you can support us by reporting any bugs you find, sending us screenshots of your projects, and starring us on Github 🌟

**Are you using React Flow or Svelte Flow at your organization and making money from it?** Awesome! We rely on your support to keep our libraries developed and maintained under an MIT License, just how we like it. For React Flow you can do that on the [React Flow Pro website](https://reactflow.dev/pro) and for both of our libraries you can do it through [Github Sponsors](https://github.com/sponsors/xyflow).

## Getting started

The best way to get started is to check out the [React Flow](https://reactflow.dev/learn) or [Svelte Flow](https://svelteflow.dev/learn) learn section. However if you want to get a sneak peek of how to install and use the libraries you can see it here: 

<details>
  <summary><strong>React Flow</strong> basic usage</summary>

  ### Installation
  
  ```sh
npm install @xyflow/react
  ```

  ### Basic usage
  ```jsx
import { useCallback } from 'react';
import {
  ReactFlow,
  MiniMap,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
  addEdge,
} from '@xyflow/react';

import '@xyflow/react/dist/style.css';

const initialNodes = [
  { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },
  { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },
];

const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];

function Flow() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
    >
      <MiniMap />
      <Controls />
      <Background />
    </ReactFlow>
  );
}

export default Flow;
```
</details>

<details>
  <summary><strong>Svelte Flow</strong> basic usage</summary>

  ### Installation
  
  ```sh
npm install @xyflow/svelte
  ```

  ### Basic usage
  ```svelte
<script lang="ts">
  import { writable } from 'svelte/store';
  import {
    SvelteFlow,
    Controls,
    Background,
    BackgroundVariant,
    MiniMap,
  } from '@xyflow/svelte';

  import '@xyflow/svelte/dist/style.css'
  
  const nodes = writable([
    {
      id: '1',
      type: 'input',
      data: { label: 'Input Node' },
      position: { x: 0, y: 0 }
    },
    {
      id: '2',
      type: 'custom',
      data: { label: 'Node' },
      position: { x: 0, y: 150 }
    }
  ]);

  const edges = writable([
    {
      id: '1-2',
      type: 'default',
      source: '1',
      target: '2',
      label: 'Edge Text'
    }
  ]);
</script>

<SvelteFlow
  {nodes}
  {edges}
  fitView
  on:nodeclick={(event) => console.log('on node click', event)}
>
  <Controls />
  <Background variant={BackgroundVariant.Dots} />
  <MiniMap />
</SvelteFlow>
```
</details>

## Releases 

For releasing packages we are using [changesets](https://github.com/changesets/changesets) in combination with the [changeset Github action](https://github.com/changesets/action). The rough idea is:

1. create PRs for new features, updates and fixes (with a changeset if relevant for changelog)
2. merge into main 
3. changset creates a PR that bumps all packages based on the changesets 
4. merge changeset PR if you want to release to Github and npm

## Built by [xyflow](https://xyflow.com)

React Flow and Svelte Flow are maintained by the [xyflow team](https://xyflow.com/about). If you need help or want to talk to us about a collaboration, reach out through our [contact form](https://xyflow.com/contact) or by joining our [Discord Server](https://discord.gg/Bqt6xrs).

## License

React Flow and Svelte Flow are [MIT licensed](./LICENSE).


## /SECURITY.md

# Security Policy

## Reporting a Vulnerability

To report a vulnerability, please privately report it via the Security tab on the correct GitHub repository ([see documentation](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)). Do not open a public issue. Provide:

- A clear description of the issue
- Steps to reproduce
- Expected vs actual behavior
- Potential impact
- A proof of concept if possible
- Affected commit / version (if known)

## Acknowledgment Timeline

We aim to acknowledge receipt of a valid report within 1 week.

## Resolution Timeline

We aim to provide a remediation plan or decision within 4 weeks. Actual fix time may be shorter or longer depending on severity, complexity, and scope.

## Scope & Threat Model

In scope:

- Vulnerabilities introduced by code in this repository
- Supply-chain risks caused by how this repository consumes or distributes its own code (e.g. insecure GitHub Actions)

Out of scope:

- Issues only present in third-party dependencies (please report those upstream)
- Issues that can only be exploited when the underlying platform (browser, server runtime) is compromised
- Denial of service via excessive legitimate use

### Supported Versions

The following versions are currently supported with security updates:

| Package        | Version | Supported              |
| -------------- | ------- | ---------------------  |
| @xyflow/react  | 12.x    | ✅ Fully supported     |
| @xyflow/svelte | 1.x     | ✅ Fully supported     |
| @xyflow/system | 0.x     | ✅ Fully supported     |
| reactflow      | > 11.x  | ⚠ Depends on severity  |
| reactflow      | < 11.x  | ❌ No longer           |

## Disclosure

Please keep reports private until a fix is released and a Security Advisory is public.

## /dependabot.yml

```yml path="/dependabot.yml" 
version: 2
updates:
  - package-ecosystem: 'npm'
    directory: '/packages/'
    schedule:
      interval: 'weekly'
    ignore:
      - dependency-type: 'development'

```

## /examples/README.md

# Examples for Testing & Development
These examples are used for feature development and E2E testing.

## Overview
SvelteKit App with Svelte Flow examples at [`examples/svelte`](./svelte) 

Vite App with React Flow examples at [`examples/react`](./react)

Astro App used for SSR testing at [`examples/astro-xyflow`](./astro-xyflow)

## E2E

For furhter documentation of E2E tests have a look at [`/tests/playwright`](/tests/playwright).

## /examples/astro-xyflow/.gitignore

```gitignore path="/examples/astro-xyflow/.gitignore" 
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store

```

## /examples/astro-xyflow/README.md

# Astro examples 

Tiny app for testing React Flow and Svelte Flow with Astro.

## Start local dev server

```sh
pnpm dev
```



## /examples/astro-xyflow/astro.config.mjs

```mjs path="/examples/astro-xyflow/astro.config.mjs" 
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';

// https://astro.build/config
export default defineConfig({
  integrations: [react(), svelte()],
});

```

## /examples/astro-xyflow/package.json

```json path="/examples/astro-xyflow/package.json" 
{
  "name": "astro-examples",
  "type": "module",
  "private": true,
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "astro": "astro"
  },
  "dependencies": {
    "@astrojs/react": "^4.3.0",
    "@astrojs/svelte": "^7.1.0",
    "@types/react": "^18.2.24",
    "@types/react-dom": "^18.2.8",
    "@xyflow/react": "workspace:^",
    "@xyflow/svelte": "workspace:^",
    "astro": "^5.13.5",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "svelte": "^5.38.7"
  }
}

```

## /examples/astro-xyflow/public/.gitkeep

```gitkeep path="/examples/astro-xyflow/public/.gitkeep" 

```

## /examples/astro-xyflow/src/components/ReactFlowExample/CustomNode.tsx

```tsx path="/examples/astro-xyflow/src/components/ReactFlowExample/CustomNode.tsx" 
import { memo, type FC, type CSSProperties } from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';

const sourceHandleStyleA: CSSProperties = { left: 50 };
const sourceHandleStyleB: CSSProperties = {
  right: 50,
  left: 'auto',
};

const CustomNode: FC<NodeProps> = ({ data, positionAbsoluteX, positionAbsoluteY }) => {
  return (
    <>
      <Handle type="target" position={Position.Top} />
      <div>
        <div>
          Label: <strong>{data.label}</strong>
        </div>
        <div>
          Position:{' '}
          <strong>
            {positionAbsoluteX.toFixed(2)},{positionAbsoluteY.toFixed(2)}
          </strong>
        </div>
      </div>

      <Handle type="source" position={Position.Bottom} id="a" style={sourceHandleStyleA} />
      <Handle type="source" position={Position.Bottom} id="b" style={sourceHandleStyleB} />
    </>
  );
};

export default memo(CustomNode);

```

## /examples/astro-xyflow/src/components/ReactFlowExample/index.tsx

```tsx path="/examples/astro-xyflow/src/components/ReactFlowExample/index.tsx" 
import { useCallback } from 'react';
import {
  ReactFlow,
  addEdge,
  useEdgesState,
  useNodesState,
  Background,
  Controls,
  type Connection,
  type Edge,
  type Node,
  Position,
} from '@xyflow/react';

import CustomNode from './CustomNode';

import '@xyflow/react/dist/style.css';

const nodeSize = {
  width: 100,
  height: 40,
};

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    ...nodeSize,
    handles: [
      {
        type: 'source',
        position: Position.Bottom,
        x: nodeSize.width * 0.5,
        y: nodeSize.height,
      },
    ],
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    ...nodeSize,
    handles: [
      {
        type: 'source',
        position: Position.Bottom,
        x: nodeSize.width * 0.5,
        y: nodeSize.height,
      },
      {
        type: 'target',
        position: Position.Top,
        x: nodeSize.width * 0.5,
        y: 0,
      },
    ],
  },
  {
    id: '3',
    data: { label: 'Node 3' },
    position: { x: 900, y: 100 },
    ...nodeSize,
    handles: [
      {
        type: 'source',
        position: Position.Bottom,
        x: nodeSize.width * 0.5,
        y: nodeSize.height,
      },
      {
        type: 'target',
        position: Position.Top,
        x: nodeSize.width * 0.5,
        y: 0,
      },
    ],
  },
  {
    id: '4',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    width: 200,
    height: 50,
    type: 'custom',
  },
];

const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e1-3', source: '1', target: '3', animated: true },
];

const nodeTypes = {
  custom: CustomNode,
};

function Flow() {
  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <div style={{ width: 700, height: 400 }}>
      <ReactFlow
        nodes={nodes}
        onNodesChange={onNodesChange}
        edges={edges}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        nodeTypes={nodeTypes}
        // fitView
        width={700}
        height={400}
        onlyRenderVisibleElements
      >
        <Background />
        <Controls />
      </ReactFlow>
    </div>
  );
}

export default Flow;

```

## /examples/astro-xyflow/src/components/ReactFlowInitialExample/CustomNode.tsx

```tsx path="/examples/astro-xyflow/src/components/ReactFlowInitialExample/CustomNode.tsx" 
import { memo, useState } from 'react';
import { Handle, Position } from '@xyflow/react';

function CustomNode() {
  const [text, setText] = useState('this is a pretty long text');

  return (
    <>
      <Handle type="target" position={Position.Top} />
      <div style={{ background: '#eee', padding: 10 }}>
        <div>
          <input value={text} onChange={(e) => setText(e.target.value)} />
          <div>text: {text}</div>
        </div>
      </div>
      <Handle type="source" position={Position.Bottom} />
    </>
  );
}

export default memo(CustomNode);

```

## /examples/astro-xyflow/src/components/ReactFlowInitialExample/index.tsx

```tsx path="/examples/astro-xyflow/src/components/ReactFlowInitialExample/index.tsx" 
import { useCallback } from 'react';
import {
  ReactFlow,
  addEdge,
  useEdgesState,
  useNodesState,
  Background,
  Controls,
  type Connection,
  type Edge,
  type Node,
} from '@xyflow/react';

import CustomNode from './CustomNode';

import '@xyflow/react/dist/style.css';

const initialNodes: Node[] = [
  {
    id: '1',
    data: {},
    position: { x: 0, y: 0 },
    initialWidth: 200,
    initialHeight: 50,
    type: 'custom',
  },
  {
    id: '2',
    data: {},
    position: { x: 0, y: 200 },
    width: 200,
    initialHeight: 50,
    type: 'custom',
  },
];

const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];

const nodeTypes = {
  custom: CustomNode,
};

function Flow() {
  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <div style={{ width: 700, height: 400 }}>
      <ReactFlow
        nodes={nodes}
        onNodesChange={onNodesChange}
        edges={edges}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        nodeTypes={nodeTypes}
        fitView
        width={700}
        height={400}
        debug
      >
        <Background />
        <Controls />
      </ReactFlow>
    </div>
  );
}

export default Flow;

```

## /examples/astro-xyflow/src/components/SvelteFlowExample/index.svelte

```svelte path="/examples/astro-xyflow/src/components/SvelteFlowExample/index.svelte" 
<script lang="ts">
  import {
    SvelteFlow,
    Controls,
    Background,
    BackgroundVariant,
    Position,
    ViewportPortal,
    type Node,
    type Edge,
  } from '@xyflow/svelte';

  import '@xyflow/svelte/dist/style.css';

  let nodes = $state.raw<Node[]>([
    {
      id: '0',
      position: { x: 0, y: 150 },
      data: { label: 'Node 0' },
      sourcePosition: Position.Right,
      targetPosition: Position.Left,
      width: 100,
      height: 40,
      handles: [
        { type: 'source', x: 100, y: 20, position: Position.Right },
        { type: 'target', x: 0, y: 20, position: Position.Left },
      ],
    },
    {
      id: 'A',
      position: { x: 250, y: 0 },
      data: { label: 'A' },
      sourcePosition: Position.Right,
      targetPosition: Position.Left,
      width: 100,
      height: 40,
      handles: [
        { type: 'source', x: 100, y: 20, position: Position.Right },
        { type: 'target', x: 0, y: 20, position: Position.Left },
      ],
    },
    {
      id: 'B',
      position: { x: 250, y: 150 },
      data: { label: 'B' },
      sourcePosition: Position.Right,
      targetPosition: Position.Left,
      width: 100,
      height: 40,
      handles: [
        { type: 'source', x: 100, y: 20, position: Position.Right },
        { type: 'target', x: 0, y: 20, position: Position.Left },
      ],
    },
    {
      id: 'C',
      position: { x: 950, y: 300 },
      data: { label: 'C' },
      sourcePosition: Position.Right,
      targetPosition: Position.Left,
      width: 100,
      height: 40,
      handles: [
        { type: 'source', x: 100, y: 20, position: Position.Right },
        { type: 'target', x: 0, y: 20, position: Position.Left },
      ],
    },
  ]);

  let edges = $state.raw<Edge[]>([
    { id: '0A', source: '0', target: 'A', animated: true },
    { id: '0B', source: '0', target: 'B', animated: true },
    { id: '0C', source: '0', target: 'C', animated: true },
  ]);

  const defaultEdgeOptions = {
    animated: true,
  };
</script>

<div style="height: 400px; width: 700px;">
  <SvelteFlow bind:nodes bind:edges onlyRenderVisibleElements {defaultEdgeOptions} width={700} height={400}>
    <Controls />
    <Background variant={BackgroundVariant.Dots} />
    <ViewportPortal target="front">
      <div style:transform="translate(100px, 100px)" style:position="absolute">[100, 100] inside the flow.</div>
    </ViewportPortal>
  </SvelteFlow>
</div>

```

## /examples/astro-xyflow/src/components/SvelteFlowInitialExample/CustomNode.svelte

```svelte path="/examples/astro-xyflow/src/components/SvelteFlowInitialExample/CustomNode.svelte" 
<script lang="ts">
  import { Handle, Position, type NodeProps } from '@xyflow/svelte';

  type $Props = NodeProps;

  let text = 'some default text';
</script>

<Handle type="target" position={Position.Left} />
<div class="custom">
  <div>
    text: {text}
  </div>
  <input type="text" bind:value={text} />
</div>
<Handle type="source" position={Position.Right} />

<style>
  .custom {
    background-color: white;
    padding: 10px;
    border: 1px solid #777;
    border-radius: 20px;
  }
</style>

```

## /examples/astro-xyflow/src/components/SvelteFlowInitialExample/index.svelte

```svelte path="/examples/astro-xyflow/src/components/SvelteFlowInitialExample/index.svelte" 
<script lang="ts">
  import { SvelteFlow, Controls, Background, BackgroundVariant, type Node, type Edge } from '@xyflow/svelte';

  import '@xyflow/svelte/dist/style.css';
  import CustomNode from './CustomNode.svelte';

  let nodes = $state.raw<Node[]>([
    {
      id: '1',
      position: { x: 0, y: 0 },
      data: {},
      initialWidth: 100,
      initialHeight: 40,
      type: 'input-node',
    },
    {
      id: '2',
      position: { x: 0, y: 150 },
      data: {},
      initialWidth: 100,
      initialHeight: 40,
      type: 'input-node',
    },
  ]);

  let edges = $state.raw<Edge[]>([{ id: '1-2', source: '1', target: '2' }]);

  const nodeTypes = {
    'input-node': CustomNode,
  };
</script>

<div style="height: 400px; width: 700px;">
  <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView width={700} height={400}>
    <Controls />
    <Background variant={BackgroundVariant.Dots} />
  </SvelteFlow>
</div>

```

## /examples/astro-xyflow/src/pages/index.astro

```astro path="/examples/astro-xyflow/src/pages/index.astro" 
---
import ReactFlowApp from '../components/ReactFlowExample';
import ReactFlowInitialApp from '../components/ReactFlowInitialExample';
import SvelteFlowApp from '../components/SvelteFlowExample/index.svelte';
import SvelteFlowInitialApp from '../components/SvelteFlowInitialExample/index.svelte';
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <title>Astro example for React Flow and Svelte Flow</title>

    <style>
      html,
      body {
        margin: 0;
        font-family: sans-serif;
      }
    </style>
  </head>
  <body>
    <h2>React Flow</h2>
    <p>no client hydration</p>
    <ReactFlowApp />

    <p>client hydration on load (client:load)</p>
    <ReactFlowApp client:load />

    <p>client hydration on load (client:load) and initialWidth / initialHeight</p>
    <ReactFlowInitialApp client:load />

    <h2>Svelte Flow</h2>
    <SvelteFlowApp />

    <p>client hydration on load (client:load)</p>
    <SvelteFlowApp client:load />

    <p>client hydration on load (client:load) and initialWidth / initialHeight</p>
    <SvelteFlowInitialApp client:load />
  </body>
</html>

```

## /examples/astro-xyflow/tsconfig.json

```json path="/examples/astro-xyflow/tsconfig.json" 
{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

```

## /examples/react/.gitignore

```gitignore path="/examples/react/.gitignore" 
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

```

## /examples/react/README.md

# React Flow examples 

This Vite app is used internally to develop and test the library.

## Start local dev server

```sh
pnpm dev
```

## Adding new example

Development of the library is done against `src/examples`. Feel free to add new implementations for features that you develop.

1. Create a new folder & flow at `src/examples/`
2. Register the new route in `src/App/routes.ts`

## Adding new E2E implementation
E2E testing is done against the flows implemented in `src/generic-tests`. Adding a new configuration file automatically adds a new route under [`http://localhost:3000/tests/generic/$foldername/$filename`](http://localhost:5173/tests/generic/nodes/general). For further documentation visit [`xyflow/tests/playwright`](/tests/playwright).





## /examples/react/cypress.config.ts

```ts path="/examples/react/cypress.config.ts" 
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000/examples',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: false,
    screenshotOnRunFailure: false,
  },
  component: {
    devServer: {
      framework: 'react',
      bundler: 'vite',
    },
    screenshotOnRunFailure: false,
    video: false,
  },
});

```

## /examples/react/cypress/components/hooks/useEdges.cy.tsx

```tsx path="/examples/react/cypress/components/hooks/useEdges.cy.tsx" 
import React, { useEffect } from 'react';
import { ReactFlow, Edge, useEdges } from '@xyflow/react';

import { nodes as initialNodes, edges as initialEdges } from '../../fixtures/simpleflow';

describe('useEdges.cy.tsx', () => {
  it('handles edges', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow nodes={initialNodes} edges={initialEdges}>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges);
  });

  it('handles defaultEdges', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow defaultNodes={initialNodes} defaultEdges={initialEdges}>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.get('@onChangeSpy').should('have.been.calledWith', []);
    cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges);
  });
});

const HookHelperComponent = ({ onChange }: { onChange: (edges: Edge[]) => void }) => {
  const edges = useEdges();

  useEffect(() => {
    onChange(edges);
  }, [edges]);

  return null;
};

```

## /examples/react/cypress/components/hooks/useNodes.cy.tsx

```tsx path="/examples/react/cypress/components/hooks/useNodes.cy.tsx" 
import { useEffect } from 'react';
import { ReactFlow, Node, useNodes } from '@xyflow/react';

import { nodes } from '../../fixtures/simpleflow';

const nodeDimensions = {
  width: 100,
  height: 50,
};

const initialNodes: Node[] = nodes.map((n) => ({
  ...n,
  style: nodeDimensions,
}));

const expectedNodes: Node[] = initialNodes.map((n) => ({
  ...n,
  measured: {
    ...nodeDimensions,
  },
}));

describe('useNodes.cy.tsx', () => {
  it('handles nodes', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow nodes={initialNodes}>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes);
  });

  it('handles defaultNodes', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow defaultNodes={initialNodes}>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.get('@onChangeSpy').should('have.been.calledWith', []);
    cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes);
  });
});

const HookHelperComponent = ({ onChange }: { onChange: (nodes: Node[]) => void }) => {
  const nodes = useNodes();

  useEffect(() => {
    onChange(nodes);
  }, [nodes]);

  return null;
};

```

## /examples/react/cypress/components/hooks/useNodesInitialized.cy.tsx

```tsx path="/examples/react/cypress/components/hooks/useNodesInitialized.cy.tsx" 
import { ReactFlow, useNodesInitialized } from '@xyflow/react';

import { nodes } from '../../fixtures/simpleflow';
import ControlledFlow from '../../support/ControlledFlow';

describe('useNodesInitialized.cy.tsx', () => {
  it('returns false for no nodes', () => {
    const initSpy = cy.spy().as('initSpy');

    cy.mount(
      <ReactFlow nodes={[]}>
        <HookHelperComponent onChange={initSpy} />
      </ReactFlow>
    );

    // cy.get('@initSpy').should('to.be.calledOnce');
    cy.get('@initSpy').should('have.been.calledWith', false);
  });

  it('returns false without onNodesChange', () => {
    const initSpy = cy.spy().as('initSpy');

    cy.mount(
      <ReactFlow nodes={nodes}>
        <HookHelperComponent onChange={initSpy} />
      </ReactFlow>
    );

    // cy.get('@initSpy').should('to.be.calledOnce');
    cy.get('@initSpy').should('have.be.calledWith', false);
  });

  it('returns true for defaultNodes', () => {
    const initSpy = cy.spy().as('initSpy');

    cy.mount(
      <ReactFlow defaultNodes={nodes}>
        <HookHelperComponent onChange={initSpy} />
      </ReactFlow>
    );

    cy.get('@initSpy').should('have.been.calledWith', false);
    cy.get('@initSpy').should('have.been.calledWith', true);
  });

  it('returns true for nodes + onNodesChange', () => {
    const initSpy = cy.spy().as('initSpy');

    cy.mount(
      <ControlledFlow initialNodes={nodes}>
        <HookHelperComponent onChange={initSpy} />
      </ControlledFlow>
    );

    cy.get('@initSpy').should('have.been.calledWith', false);
    cy.get('@initSpy').should('have.been.calledWith', true);
  });
});

// test specific helpers

function HookHelperComponent({ onChange }: { onChange: (init: boolean) => void }) {
  const initialized = useNodesInitialized();

  onChange(initialized);

  return null;
}

```

## /examples/react/cypress/components/hooks/useOnViewportChange.cy.tsx

```tsx path="/examples/react/cypress/components/hooks/useOnViewportChange.cy.tsx" 
import { ReactFlow, useOnViewportChange, Viewport } from '@xyflow/react';

describe('useOnViewportChange.cy.tsx', () => {
  it('listen to viewport drag', () => {
    const onStartSpy = cy.spy().as('onStartSpy');
    const onChangeSpy = cy.spy().as('onChangeSpy');
    const onEndSpy = cy.spy().as('onEndSpy');

    cy.mount(
      <ReactFlow>
        <HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
      </ReactFlow>
    );

    cy.dragPane({ from: { x: 50, y: 50 }, to: { x: 50, y: 100 } }).then(() => {
      cy.get('@onStartSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 });
      cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 50, zoom: 1 });
      cy.get('@onEndSpy').should('have.been.calledWith', { x: 0, y: 50, zoom: 1 });
    });
  });

  it('handles zoom in', () => {
    const onStartSpy = cy.spy().as('onStartSpy');
    const onChangeSpy = cy.spy().as('onChangeSpy');
    const onEndSpy = cy.spy().as('onEndSpy');

    cy.mount(
      <ReactFlow>
        <HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
      </ReactFlow>
    );

    cy.zoomPane(-200).then(() => {
      cy.get('@onStartSpy').should('have.been.callCount', 1);
      cy.get('@onChangeSpy').should('have.been.callCount', 1);
      cy.get('@onEndSpy').should('have.been.callCount', 1);

      expect(getLatestViewport(onChangeSpy).zoom).to.be.gt(1);
    });
  });

  it('handles zoom out', () => {
    const onStartSpy = cy.spy().as('onStartSpy');
    const onChangeSpy = cy.spy().as('onChangeSpy');
    const onEndSpy = cy.spy().as('onEndSpy');

    cy.mount(
      <ReactFlow>
        <HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
      </ReactFlow>
    );

    cy.zoomPane(200).then(() => {
      cy.get('@onStartSpy').should('have.been.callCount', 1);
      cy.get('@onChangeSpy').should('have.been.callCount', 1);
      cy.get('@onEndSpy').should('have.been.callCount', 1);

      expect(getLatestViewport(onChangeSpy).zoom).to.be.lt(1);
    });
  });

  it('handles default viewport', () => {
    const defaultViewport = { x: 100, y: 100, zoom: 0.5 };
    const onStartSpy = cy.spy().as('onStartSpy');

    cy.mount(
      <ReactFlow defaultViewport={defaultViewport}>
        <HookHelperComponent onStart={onStartSpy} />
      </ReactFlow>
    );

    cy.dragPane({ from: { x: 50, y: 50 }, to: { x: 50, y: 100 } }).then(() => {
      cy.get('@onStartSpy').should('have.been.calledWith', defaultViewport);
    });
  });
});

// test specific helpers

function HookHelperComponent({
  onStart,
  onChange,
  onEnd,
}: {
  onStart?: (viewport: Viewport) => void;
  onChange?: (viewport: Viewport) => void;
  onEnd?: (viewport: Viewport) => void;
}) {
  useOnViewportChange({ onStart, onChange, onEnd });

  return null;
}

function getLatestViewport(onChangeSpy: any) {
  return onChangeSpy.lastCall.args[0] as unknown as Viewport;
}

```

## /examples/react/cypress/components/hooks/useViewport.cy.tsx

```tsx path="/examples/react/cypress/components/hooks/useViewport.cy.tsx" 
import { ReactFlow, useViewport, Viewport } from '@xyflow/react';

describe('useViewport.cy.tsx', () => {
  it('handles drag', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 });

    cy.dragPane({ from: { x: 50, y: 0 }, to: { x: 50, y: 400 } }).then(() => {
      cy.get('@onChangeSpy').should('have.been.callCount', 2);
      cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 });

      expect(getLatestViewport(onChangeSpy)).to.eql({ x: 0, y: 400, zoom: 1 });
    });
  });

  it('handles zoom in', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.zoomPane(-200).then(() => {
      cy.get('@onChangeSpy').should('have.been.callCount', 2);
      expect(getLatestViewport(onChangeSpy).zoom).to.be.gt(1);
    });
  });

  it('handles zoom out', () => {
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.zoomPane(200).then(() => {
      cy.get('@onChangeSpy').should('have.been.callCount', 2);
      expect(getLatestViewport(onChangeSpy).zoom).to.be.lt(1);
    });
  });

  it('handles default viewport', () => {
    const defaultViewport = { x: 100, y: 100, zoom: 0.5 };
    const onChangeSpy = cy.spy().as('onChangeSpy');

    cy.mount(
      <ReactFlow defaultViewport={defaultViewport}>
        <HookHelperComponent onChange={onChangeSpy} />
      </ReactFlow>
    );

    cy.get('@onChangeSpy').should('have.been.calledWith', defaultViewport);
  });
});

// test specific helpers

function HookHelperComponent({ onChange }: { onChange: (vp: Viewport) => void }) {
  const viewport = useViewport();

  onChange(viewport);

  return null;
}

function getLatestViewport(onChangeSpy: any) {
  return onChangeSpy.lastCall.args[0] as unknown as Viewport;
}

```

## /examples/react/cypress/components/reactflow/basic-props.cy.tsx

```tsx path="/examples/react/cypress/components/reactflow/basic-props.cy.tsx" 
import { ReactFlow, EdgeProps } from '@xyflow/react';

import ControlledFlow from '../../support/ControlledFlow';
import * as simpleflow from '../../fixtures/simpleflow';

describe('<ReactFlow />: Basic Props', () => {
  describe('uses defaultNodes and defaultEdges', () => {
    beforeEach(() => {
      cy.mount(<ReactFlow defaultNodes={simpleflow.nodes} defaultEdges={simpleflow.edges} nodeDragThreshold={0} />);
    });

    it('mounts nodes and edges', () => {
      cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
      cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length);
    });

    it('drags a node', () => {
      const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');

      cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
        const styleAfterDrag = $el.css('transform');
        expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
      });
    });
  });

  describe('uses nodes and edges', () => {
    beforeEach(() => {
      cy.mount(
        <ControlledFlow
          addOnNodeChangeHandler={false}
          initialNodes={simpleflow.nodes}
          initialEdges={simpleflow.edges}
        />
      );
    });

    it('mounts nodes and edges', () => {
      cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
      cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length);
    });

    it('can not drag a node', () => {
      const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');

      cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
        const styleAfterDrag = $el.css('transform');
        expect(styleBeforeDrag).to.equal(styleAfterDrag);
      });
    });
  });

  describe('uses onNodesChange handler', () => {
    beforeEach(() => {
      cy.mount(<ControlledFlow addOnConnectHandler={false} initialNodes={simpleflow.nodes} />);
    });

    it('mounts nodes', () => {
      cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
      cy.get('.react-flow__edge').should('have.length', 0);
    });

    it('drags a node', () => {
      const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');

      cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
        const styleAfterDrag = $el.css('transform');
        expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
      });
    });

    it('can not connect nodes', () => {
      cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });

      cy.get('.react-flow__node')
        .last()
        .find('.react-flow__handle.target')
        .trigger('mousemove')
        .trigger('mouseup', { force: true });

      cy.get('.react-flow__edge').should('have.length', 0);
    });
  });

  describe('uses onEdgeChange handler', () => {
    beforeEach(() => {
      cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} initialEdges={simpleflow.edges} />);
    });

    it('mounts nodes and edges', () => {
      cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
      cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length);
    });

    it('selects an edge', () => {
      cy.get('.react-flow__edge').first().click().should('have.class', 'selected');
    });
  });

  describe('uses onConnect handlers', () => {
    beforeEach(() => {
      cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} />);
    });

    it('mounts nodes', () => {
      cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length);
      cy.get('.react-flow__edge').should('have.length', 0);
    });

    it('connects nodes', () => {
      cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });

      cy.get('.react-flow__node')
        .last()
        .find('.react-flow__handle.target')
        .trigger('mousemove')
        .trigger('mouseup', { force: true });

      cy.get('.react-flow__edge').should('have.length', 1);
    });
  });

  describe('uses nodeTypes', () => {
    it('renders custom nodes', () => {
      const nodeTypes = {
        custom: () => <div className="customnode">custom node</div>,
      };

      const initialNodes = simpleflow.nodes.map((n) => ({
        ...n,
        type: 'custom',
      }));

      cy.mount(<ControlledFlow nodeTypes={nodeTypes} initialNodes={initialNodes} />);

      cy.get('.react-flow__node-custom').should('have.length', simpleflow.nodes.length);
      cy.get('.react-flow').contains('custom node');
    });

    it('tries invalid node type - should fallback to default', () => {
      const initialNodes = simpleflow.nodes.map((n) => ({
        ...n,
        type: 'invalid',
      }));

      cy.mount(<ControlledFlow initialNodes={initialNodes} />);

      cy.get('.react-flow__node-invalid').should('have.length', 0);
      cy.get('.react-flow__node-default').should('have.length', simpleflow.nodes.length);
    });
  });

  describe('uses edgeTypes', () => {
    it('renders custom edge', () => {
      const edgeTypes = {
        custom: ({ sourceX, sourceY, targetX, targetY }: EdgeProps) => (
          <path d={`M${sourceX} ${sourceY} L${targetX} ${targetY}`} stroke="black" />
        ),
      };

      const initialEdges = simpleflow.edges.map((e) => ({
        ...e,
        type: 'custom',
      }));

      cy.mount(<ControlledFlow edgeTypes={edgeTypes} initialNodes={simpleflow.nodes} initialEdges={initialEdges} />);

      cy.get('.react-flow__edge-custom').should('have.length', simpleflow.edges.length);
    });

    it('tries invalid edge type - should fallback to default', () => {
      const initialEdges = simpleflow.edges.map((e) => ({
        ...e,
        type: 'invalid',
      }));

      cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} initialEdges={initialEdges} />);

      cy.get('.react-flow__edge-invalid').should('have.length', 0);
      cy.get('.react-flow__edge-default').should('have.length', simpleflow.edges.length);
    });
  });

  it('uses style', () => {
    const styles = {
      backgroundColor: 'red',
    };

    cy.mount(<ControlledFlow style={styles} />);
    cy.get('.react-flow').should('have.css', 'background-color', 'rgb(255, 0, 0)');
  });

  it('uses classname', () => {
    cy.mount(<ControlledFlow className="custom" />);
    cy.get('.react-flow').should('have.class', 'custom');
  });
});

```

## /examples/react/cypress/components/reactflow/event-handlers.cy.tsx

```tsx path="/examples/react/cypress/components/reactflow/event-handlers.cy.tsx" 
import { skipOn } from '@cypress/skip-test';

import { nodes, edges } from '../../fixtures/simpleflow';
import ControlledFlow from '../../support/ControlledFlow';

describe('<ReactFlow />: Event handlers', () => {
  describe('Node event handlers', () => {
    it('handles onInit', () => {
      const onInitSpy = cy.spy().as('onInitSpy');

      cy.mount(<ControlledFlow initialNodes={nodes} initialEdges={edges} onInit={onInitSpy} />)
        .wait(100)
        .then(() => {
          expect(onInitSpy.callCount).to.be.eq(1);
        });
    });

    it('handles onNodeClick', () => {
      const onNodeClickSpy = cy.spy().as('onNodeClickSpy');

      cy.mount(<ControlledFlow initialNodes={nodes} initialEdges={edges} onNodeClick={onNodeClickSpy} />).then(() => {
        expect(onNodeClickSpy.callCount).to.be.eq(0);
        cy.get('.react-flow__node:first')
          .click()
          .then(() => {
            expect(onNodeClickSpy.callCount).to.be.eq(1);
          });
      });
    });

    it('handles onNodeDrag handlers', () => {
      const onNodeDragStart = cy.spy().as('onNodeDragStart');
      const onNodeDrag = cy.spy().as('onNodeDrag');
      const onNodeDragStop = cy.spy().as('onNodeDragStop');

      cy.mount(
        <ControlledFlow
          initialNodes={nodes}
          initialEdges={edges}
          onNodeDragStart={onNodeDragStart}
          onNodeDrag={onNodeDrag}
          onNodeDragStop={onNodeDragStop}
        />
      ).then(() => {
        expect(onNodeDragStart.callCount).to.be.eq(0);
        expect(onNodeDrag.callCount).to.be.eq(0);
        expect(onNodeDragStop.callCount).to.be.eq(0);

        cy.drag('.react-flow__node:first', { x: 200, y: 0 }).then(() => {
          expect(onNodeDragStart.callCount).to.be.gt(0);
          expect(onNodeDrag.callCount).to.be.gt(0);
          expect(onNodeDragStop.callCount).to.be.gt(0);
        });
      });
    });

    it('handles onNodeMouse handlers', () => {
      const onNodeMouseEnter = cy.spy().as('onNodeMouseEnter');
      const onNodeMouseMove = cy.spy().as('onNodeMouseMove');
      const onNodeMouseLeave = cy.spy().as('onNodeMouseLeave');
      const onNodeContextMenu = cy.spy().as('onNodeContextMenu');
      const onNodeDoubleClick = cy.spy().as('onNodeDoubleClick');

      cy.mount(
        <ControlledFlow
          initialNodes={nodes}
          initialEdges={edges}
          onNodeMouseEnter={onNodeMouseEnter}
          onNodeMouseMove={onNodeMouseMove}
          onNodeMouseLeave={onNodeMouseLeave}
          onNodeContextMenu={onNodeContextMenu}
          onNodeDoubleClick={onNodeDoubleClick}
        />
      )
        .wait(200)
        .then(() => {
          expect(onNodeMouseEnter.callCount).to.be.eq(0);
          expect(onNodeMouseMove.callCount).to.be.eq(0);
          expect(onNodeMouseLeave.callCount).to.be.eq(0);
          expect(onNodeContextMenu.callCount).to.be.eq(0);
          expect(onNodeDoubleClick.callCount).to.be.eq(0);

          const node = cy.get('.react-flow__node').contains('Node 1');

          node
            .rightclick()
            .dblclick()
            .then(() => {
              expect(onNodeContextMenu.callCount).to.be.eq(1);
              expect(onNodeDoubleClick.callCount).to.be.eq(1);
            });

          skipOn('firefox');

          node
            .realHover()
            .realMouseMove(100, 100)
            .then(() => {
              expect(onNodeMouseEnter.callCount).to.be.gt(0);
              expect(onNodeMouseMove.callCount).to.be.gt(0);
              expect(onNodeMouseLeave.callCount).to.be.gt(0);
            });
        });
    });
  });

  describe('Edge event handlers', () => {
    it('handles onEdgeClick', () => {
      const onEdgeClick = cy.spy().as('onEdgeClick');

      cy.mount(<ControlledFlow initialNodes={nodes} initialEdges={edges} onEdgeClick={onEdgeClick} />).then(() => {
        expect(onEdgeClick.callCount).to.be.eq(0);
        cy.get('.react-flow__edge:first')
          .click()
          .then(() => {
            expect(onEdgeClick.callCount).to.be.eq(1);
          });
      });
    });

    it('handles onEdgeMouse handlers', () => {
      const onEdgeMouseEnter = cy.spy().as('onEdgeMouseEnter');
      const onEdgeMouseMove = cy.spy().as('onEdgeMouseMove');
      const onEdgeMouseLeave = cy.spy().as('onEdgeMouseLeave');
      const onEdgeContextMenu = cy.spy().as('onEdgeContextMenu');
      const onEdgeDoubleClick = cy.spy().as('onEdgedoubleClick');

      cy.mount(
        <ControlledFlow
          initialNodes={nodes}
          initialEdges={edges}
          onEdgeMouseEnter={onEdgeMouseEnter}
          onEdgeMouseMove={onEdgeMouseMove}
          onEdgeMouseLeave={onEdgeMouseLeave}
          onEdgeContextMenu={onEdgeContextMenu}
          onEdgeDoubleClick={onEdgeDoubleClick}
        />
      )
        .wait(200)
        .then(() => {
          expect(onEdgeMouseEnter.callCount).to.be.eq(0);
          expect(onEdgeMouseMove.callCount).to.be.eq(0);
          expect(onEdgeMouseLeave.callCount).to.be.eq(0);
          expect(onEdgeContextMenu.callCount).to.be.eq(0);
          expect(onEdgeDoubleClick.callCount).to.be.eq(0);

          const edge = cy.get('.react-flow__edge:first');

          edge
            .rightclick()
            .dblclick()
            .then(() => {
              expect(onEdgeContextMenu.callCount).to.be.eq(1);
            });

          skipOn('firefox');

          edge
            .realHover()
            .realMouseMove(100, 100)
            .then(() => {
              expect(onEdgeMouseEnter.callCount).to.be.gt(0);
              expect(onEdgeMouseMove.callCount).to.be.gt(0);
              expect(onEdgeMouseLeave.callCount).to.be.gt(0);
            });
        });
    });
  });

  describe('Pane event handlers', () => {
    it('handles onMove handlers', () => {
      const onMoveStart = cy.spy().as('onMoveStart');
      const onMove = cy.spy().as('onMove');
      const onMoveEnd = cy.spy().as('onMoveEnd');

      cy.mount(<ControlledFlow onMoveStart={onMoveStart} onMove={onMove} onMoveEnd={onMoveEnd} />).then(() => {
        expect(onMoveStart.callCount).to.be.eq(0);
        expect(onMove.callCount).to.be.eq(0);
        expect(onMoveEnd.callCount).to.be.eq(0);

        cy.dragPane({ from: { x: 10, y: 200 }, to: { x: 100, y: 200 } })
          .wait(100)
          .then(() => {
            expect(onMoveStart.callCount).to.be.eq(1);
            expect(onMove.callCount).to.be.gt(0);
            expect(onMoveEnd.callCount).to.be.eq(1);
          });
      });
    });

    it('handles click handlers', () => {
      const onPaneClick = cy.spy().as('onPaneClick');
      const onPaneContextMenu = cy.spy().as('onPaneContextMenu');

      cy.mount(<ControlledFlow onPaneClick={onPaneClick} onPaneContextMenu={onPaneContextMenu} />).then(() => {
        expect(onPaneClick.callCount).to.be.eq(0);
        expect(onPaneContextMenu.callCount).to.be.eq(0);

        cy.get('.react-flow__pane')
          .rightclick()
          .click()
          .wait(100)
          .then(() => {
            expect(onPaneClick.callCount).to.be.eq(1);
            expect(onPaneContextMenu.callCount).to.be.eq(1);
          });
      });
    });
  });
});

```

## /examples/react/cypress/components/reactflow/multiple-instance.cy.tsx

```tsx path="/examples/react/cypress/components/reactflow/multiple-instance.cy.tsx" 
import { ReactFlow, BaseEdge, EdgeLabelRenderer, EdgeProps, getSmoothStepPath, ReactFlowProvider } from '@xyflow/react';
import * as simpleflow from '../../fixtures/simpleflow';

function CustomEdge(props: EdgeProps) {
  const [path, labelX, labelY] = getSmoothStepPath(props);
  return (
    <>
      <BaseEdge path={path} labelX={labelX} labelY={labelY} />
      <EdgeLabelRenderer>
        <div className="label">{props.id}</div>
      </EdgeLabelRenderer>
    </>
  );
}

const simpleflow1 = { ...simpleflow };
simpleflow1.edges = [...simpleflow1.edges];
simpleflow1.edges[0] = { ...simpleflow1.edges[0], id: 'edge1' };

const simpleflow2 = { ...simpleflow };
simpleflow2.edges = [...simpleflow2.edges];
simpleflow2.edges[0] = { ...simpleflow2.edges[0], id: 'edge2' };

describe('<ReactFlow />: Multiple Instances', () => {
  describe('render EdgeLabelRenderer', () => {
    beforeEach(() => {
      cy.mount(
        <>
          <ReactFlowProvider>
            <ReactFlow
              defaultNodes={simpleflow1.nodes}
              edgeTypes={{ default: CustomEdge }}
              defaultEdges={simpleflow1.edges}
            />
          </ReactFlowProvider>
          <ReactFlowProvider>
            <ReactFlow
              defaultNodes={simpleflow2.nodes}
              edgeTypes={{ default: CustomEdge }}
              defaultEdges={simpleflow2.edges}
            />
          </ReactFlowProvider>
        </>
      );
    });

    it('Each ReactFlow instance has one edge label in EdgeLabelRenderer', () => {
      cy.get('.react-flow__edgelabel-renderer').should('have.length', 2);

      cy.get('.react-flow__edgelabel-renderer')
        .eq(0)
        .within(() => {
          cy.get('.label').should('have.length', 1).should('contain.text', 'edge1');
        });

      cy.get('.react-flow__edgelabel-renderer')
        .eq(1)
        .within(() => {
          cy.get('.label').should('have.length', 1).should('contain.text', 'edge2');
        });
    });
  });
});

```

## /examples/react/cypress/components/reactflow/on-nodes-change.cy.tsx

```tsx path="/examples/react/cypress/components/reactflow/on-nodes-change.cy.tsx" 
import { useCallback, useState } from 'react';
import { OnNodesChange, Panel, ReactFlow, ReactFlowProps, applyNodeChanges, useReactFlow, Node } from '@xyflow/react';

const nodes = [
  {
    id: '1',
    data: { label: 'Node 1' },
    position: { x: 0, y: 0 },
    style: { width: 100, height: 40 },
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 200, y: 200 },
    style: { width: 80, height: 50 },
  },
];

describe('<ReactFlow />: onNodesChange', () => {
  beforeEach(() => {
    const onNodesChange = cy.spy().as('onNodesChange');
    cy.mount(<Comp nodes={nodes} onNodesChange={onNodesChange} />).wait(500);
  });

  it('receive initial dimension change', () => {
    const expectedChanges = nodes.map((n) => ({
      type: 'dimensions',
      id: n.id,
      dimensions: n.style,
    }));

    cy.get('@onNodesChange').should('have.callCount', 1);
    cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
  });

  it('receive replace and dimension change', () => {
    const expectedDimensionChanges = [
      {
        type: 'dimensions',
        id: '1',
        dimensions: { width: 200, height: 100 },
      },
    ];

    const expectedReplaceChanges = [
      {
        type: 'replace',
        id: '1',
        item: {
          ...nodes[0],
          measured: { width: 200, height: 100 },
          style: { width: 200, height: 100 },
        },
      },
    ];

    cy.get('[data-id=update-btn]').click();

    cy.get('@onNodesChange').should('have.callCount', 3);
    cy.get('@onNodesChange').then((s) => {
      // dimension change (already checked above)
      // const firstCall = s.getCall(0);
      // replace change
      // @ts-ignore
      const secondChangeCallArgs = s.getCall(1).args[0];
      // dimension change
      // @ts-ignore
      const thirdChangeCallArgs = s.getCall(2).args[0];

      expect(secondChangeCallArgs).to.be.deep.eq(expectedReplaceChanges);
      expect(thirdChangeCallArgs).to.be.deep.eq(expectedDimensionChanges);
    });
  });

  it('receive select and unselect change', () => {
    const expectedSelectChanges = [{ id: '1', type: 'select', selected: true }];
    const expectedUnselectChanges = [{ id: '1', type: 'select', selected: false }];

    cy.get('.react-flow__node').first().click();
    cy.get('@onNodesChange').should('have.calledWith', expectedSelectChanges);

    cy.get('.react-flow__pane').first().click({ force: true });
    cy.get('@onNodesChange').should('have.calledWith', expectedUnselectChanges);
  });

  it('receive position change', () => {
    const endPosition = { x: 200, y: 25 };
    const expectedChanges = [
      { id: '1', type: 'position', dragging: false, position: endPosition, positionAbsolute: endPosition },
    ];

    cy.drag('.react-flow__node:first', endPosition).then(() => {
      cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
    });
  });

  it('receive remove change', () => {
    const expectedChanges = [{ id: '1', type: 'remove' }];

    cy.get('.react-flow__node').first().click();
    cy.realPress('Backspace');

    cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
  });
});

// test specific helpers

function UpdateButton() {
  const { updateNode } = useReactFlow();
  const updateNodeDimensions = () => {
    updateNode('1', { style: { width: 200, height: 100 } });
  };

  return (
    <Panel position="top-right">
      <button data-id="update-btn" onClick={updateNodeDimensions} id="btn">
        update
      </button>
    </Panel>
  );
}

type CompProps = ReactFlowProps & { onNodesChange: (changes: any) => void };

function Comp(props: CompProps) {
  const [nodes, setNodes] = useState(props.nodes || []);
  const onNodesChange: OnNodesChange<Node> = useCallback((changes) => {
    props.onNodesChange(changes);
    setNodes((nds) => applyNodeChanges(changes, nds));
  }, []);

  return (
    <ReactFlow nodes={nodes} onNodesChange={onNodesChange} nodeDragThreshold={0}>
      <UpdateButton />
    </ReactFlow>
  );
}

```

## /examples/react/cypress/components/reactflow/uncontrolled.cy.tsx

```tsx path="/examples/react/cypress/components/reactflow/uncontrolled.cy.tsx" 
import { ReactFlow } from '@xyflow/react';

import { nodes, edges } from '../../fixtures/simpleflow';

describe('<ReactFlow />: Uncontrolled Flow', () => {
  beforeEach(() => {
    cy.mount(<ReactFlow defaultNodes={nodes} defaultEdges={edges} />);
  });

  it('mounts nodes and edges', () => {
    cy.get('.react-flow__node').should('have.length', nodes.length);
    cy.get('.react-flow__edge').should('have.length', edges.length);
  });

  it('selects a node', () => {
    cy.get('.react-flow__node').first().click().should('have.class', 'selected');
    cy.get('.react-flow__pane').click();
    cy.get('.react-flow__node').first().should('not.have.class', 'selected');
  });

  it('drags a node', () => {
    const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');

    cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery<HTMLElement>) => {
      const styleAfterDrag = $el.css('transform');
      expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
    });
  });

  it('selects an edge', () => {
    cy.get('.react-flow__edge').first().click().should('have.class', 'selected');
    cy.get('.react-flow__pane').click();
    cy.get('.react-flow__edge').first().should('not.have.class', 'selected');
  });
});

```

## /examples/react/cypress/components/reactflow/view-props.cy.tsx

```tsx path="/examples/react/cypress/components/reactflow/view-props.cy.tsx" 
import { ReactFlow, ReactFlowProps, Viewport, useViewport, SnapGrid, CoordinateExtent, Node } from '@xyflow/react';

import ControlledFlow from '../../support/ControlledFlow';
import * as simpleflow from '../../fixtures/simpleflow';

const nodesOutsideOfView = [
  {
    ...simpleflow.nodes[0],
    position: {
      x: -500,
      y: 0,
    },
  },
  {
    ...simpleflow.nodes[1],
    position: {
      x: 500,
      y: 0,
    },
  },
];

describe('<ReactFlow />: View Props', () => {
  it('uses fitView', () => {
    cy.mount(<Comp nodes={nodesOutsideOfView} fitView minZoom={0.2} />).wait(200);
    cy.get('.react-flow__node:first').isWithinViewport();
  });

  it('uses fitViewOptions', () => {
    const fitViewOptions = { padding: 0.5 };

    cy.mount(<ReactFlow nodes={[nodesOutsideOfView[0]]} fitView fitViewOptions={fitViewOptions} />).wait(200);
    cy.get('.react-flow__node:first').isWithinViewport();
  });

  describe('uses minZoom', () => {
    it('minZoom: 0.2', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');

      cy.mount(<Comp minZoom={0.2} onChange={onChangeSpy} />);
      cy.zoomPane(10000).then(() => {
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(0.2);
      });
    });

    it('minZoom: 1', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');

      cy.mount(<Comp minZoom={1} onChange={onChangeSpy} />);
      cy.zoomPane(10000).then(() => {
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
      });
    });

    it('sets invalid defaultZoom', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 0, y: 0, zoom: 0.2 };

      cy.mount(<Comp minZoom={1} defaultViewport={defaultViewport} onChange={onChangeSpy} />);
      cy.zoomPane(10000).then(() => {
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
      });
    });
  });

  describe('uses maxZoom', () => {
    it('maxZoom: 1', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');

      cy.mount(<Comp maxZoom={1} onChange={onChangeSpy} />);
      cy.zoomPane(-10000).then(() => {
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
      });
    });

    it('maxZoom: 2', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');

      cy.mount(<Comp maxZoom={2} onChange={onChangeSpy} />);
      cy.zoomPane(-10000).then(() => {
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(2);
      });
    });

    it('sets invalid defaultZoom', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 0, y: 0, zoom: 2 };

      cy.mount(<Comp maxZoom={1.5} defaultViewport={defaultViewport} onChange={onChangeSpy} />);
      cy.zoomPane(-10000).then(() => {
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1.5);
      });
    });
  });

  describe('uses defaultViewport', () => {
    it('defaultViewport: { x: 0, y: 0, zoom: 1 }', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 0, y: 0, zoom: 1 };

      cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
        expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport);
      });
    });

    it('defaultViewport: { x: 0, y: 0, zoom: 1.5 }', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 0, y: 0, zoom: 1.5 };

      cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
        expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport);
      });
    });

    it('defaultViewport: { x: 100, y: 100, zoom: 1.5 }', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 100, y: 100, zoom: 1.5 };

      cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
        expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport);
      });
    });

    it('defaultViewport: invalid { x: 0, y: 0, zoom: -1 }', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 0, y: 0, zoom: -1 };

      cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
        // this should be min zoom because zoom is clamped
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eql(0.5);
      });
    });

    it('defaultViewport: invalid { x: 0, y: 0, zoom: 100 }', () => {
      const onChangeSpy = cy.spy().as('onChangeSpy');
      const defaultViewport = { x: 0, y: 0, zoom: 100 };

      cy.mount(<Comp nodes={simpleflow.nodes} defaultViewport={defaultViewport} onChange={onChangeSpy} />).then(() => {
        // this should be max zoom because zoom is clamped
        expect(getLatestViewport(onChangeSpy).zoom).to.be.eql(2);
      });
    });
  });

  it('uses snapGrid and snapToGrid', () => {
    const snapGrid: SnapGrid = [100, 100];

    cy.mount(<ControlledFlow initialNodes={simpleflow.nodes} snapGrid={snapGrid} snapToGrid />).then(() => {
      cy.drag('.react-flow__node:first', { x: 125, y: 125 }).then(($el: any) => {
        const transformAfterDrag = $el.css('transform');
        const { m41: nodeX, m42: nodeY } = new DOMMatrixReadOnly(transformAfterDrag);
        expect([nodeX, nodeY]).to.eql(snapGrid);
      });
    });
  });

  describe('uses onlyRenderVisibleElements', () => {
    it('displays no nodes', () => {
      cy.mount(<ControlledFlow initialNodes={nodesOutsideOfView} onlyRenderVisibleElements />).then(() => {
        cy.get('.react-flow__node').should('have.length', 0);
      });
    });

    it('displays one node', () => {
      const nodes = nodesOutsideOfView.map((n) => {
        if (n.id === '1') {
          return { ...n, position: { x: 0, y: 0 } };
        }
        return n;
      });
      cy.mount(<ControlledFlow initialNodes={nodes} onlyRenderVisibleElements />).then(() => {
        cy.get('.react-flow__node').should('have.length', 1);
      });
    });
  });

  it('uses translateExtent', () => {
    const translateExtent: CoordinateExtent = [
      [0, 0],
      [1000, 1000],
    ];

    cy.mount(<ControlledFlow translateExtent={translateExtent} />).then(() => {
      const transformBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');

      cy.dragPane({ from: { x: 0, y: 0 }, to: { x: 100, y: 100 } }).then(() => {
        const transformAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
        expect(transformBeforeDrag).to.eql(transformAfterDrag);
      });
    });
  });

  it('uses nodeExtent', () => {
    const nodes: Node[] = [
      {
        id: '1',
        position: { x: 200, y: 200 },
        data: { label: '1' },
      },
    ];

    const nodeExtent: CoordinateExtent = [
      [0, 0],
      [50, 50],
    ];

    cy.mount(<ControlledFlow nodes={nodes} nodeExtent={nodeExtent} />)
      .wait(500)
      .then(() => {
        const transform = Cypress.$('.react-flow__node:first').css('transform');
        const { m41: nodeX, m42: nodeY } = new DOMMatrixReadOnly(transform);
        expect(nodeX).to.equal(50);
        expect(nodeY).to.equal(50);
      });
  });

  describe('uses preventScrolling', () => {
    function ScrollFlow({ preventScrolling }: { preventScrolling: boolean }) {
      return (
        <div className="wrapper" style={{ height: 2000 }}>
          <div className="inner" style={{ height: 500 }}>
            <ControlledFlow preventScrolling={preventScrolling} />
          </div>
        </div>
      );
    }

    it('lets user scroll', () => {
      cy.mount(<ScrollFlow preventScrolling={false} />).then(() => {
        cy.window().then((window) => {
          cy.get('.react-flow__pane').trigger('wheel', {
            wheelDelta: 240,
            wheelDeltaX: 0,
            wheelDeltaY: 240,
            eventConstructor: 'WheelEvent',
            view: window,
          });

          // @TODO: why is this not working?
          // it seems that the event is somehow broken. This works fine when using the mouse.
          // cy.window().its('scrollY').should('be.gt', 0);
        });
      });
    });

    it('does not user scroll', () => {
      cy.mount(<ScrollFlow preventScrolling={true} />).then(() => {
        cy.window().then((window) => {
          cy.get('.react-flow__pane').trigger('wheel', {
            wheelDelta: 240,
            wheelDeltaX: 0,
            wheelDeltaY: 240,
            eventConstructor: 'WheelEvent',
            view: window,
          });

          cy.window().its('scrollY').should('be.equal', 0);
        });
      });
    });
  });

  describe('uses attributionPosition', () => {
    it('displays it on the bottom right', () => {
      cy.mount(<ControlledFlow />).then(() => {
        cy.get('.react-flow__attribution').then(($el) => {
          const { left, top, width, height } = $el[0].getBoundingClientRect();

          expect(left).equal(window.innerWidth - width);
          expect(top).equal(window.innerHeight - height);
        });
      });
    });

    it('displays it on the top left', () => {
      cy.mount(<ControlledFlow attributionPosition="top-left" />).then(() => {
        cy.get('.react-flow__attribution').then(($el) => {
          const { left, top } = $el[0].getBoundingClientRect();

          expect(left).equal(0);
          expect(top).equal(0);
        });
      });
    });
  });
});

// test specific helpers

type CompProps = {
  onChange?: (vp: Viewport) => void;
};

function InnerComp({ onChange }: CompProps) {
  const viewport = useViewport();

  onChange?.(viewport);

  return null;
}

function Comp({ onChange, ...rest }: CompProps & ReactFlowProps) {
  return (
    <ReactFlow {...rest}>
      <InnerComp onChange={onChange} />
    </ReactFlow>
  );
}

function getLatestViewport(onChangeSpy: any) {
  return onChangeSpy.lastCall.args[0] as unknown as Viewport;
}

```

## /examples/react/cypress/components/utils/adopt-user-nodes.cy.ts

```ts path="/examples/react/cypress/components/utils/adopt-user-nodes.cy.ts" 
import { adoptUserNodes, type NodeLookup, type ParentLookup } from '@xyflow/system';
import type { CoordinateExtent, Node, NodeOrigin } from '@xyflow/react';

describe('adoptUserNodes Testing', () => {
  it('builds node lookup', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const userNode: Node = {
      id: '1',
      data: { label: 'node' },
      position: { x: 250, y: 5 },
      measured: { width: 100, height: 50 },
    };

    adoptUserNodes([userNode], nodeLookup, parentLookup);
    const internalNode = nodeLookup.get('1');

    expect(nodeLookup.size).to.equal(1);
    expect(internalNode).to.have.property('internals');
  });

  it('calculates positionAbsolute with nodeOrigin', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const userNode: Node = {
      id: '1',
      data: { label: 'node' },
      position: { x: 250, y: 5 },
      measured: { width: 100, height: 50 },
    };

    adoptUserNodes([userNode], nodeLookup, parentLookup, {
      nodeOrigin: [0.5, 0.5],
    });

    const internalNode = nodeLookup.get('1');

    expect(internalNode?.internals.positionAbsolute.x).to.equal(
      userNode.position.x - (userNode.measured?.width ?? 0) / 2
    );
    expect(internalNode?.internals.positionAbsolute.y).to.equal(
      userNode.position.y - (userNode.measured?.height ?? 0) / 2
    );
  });

  it('calculates positionAbsolute with nodeExtent', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();
    const nodeExtent: CoordinateExtent = [
      [0, 0],
      [100, 100],
    ];

    const userNode: Node = {
      id: '1',
      data: { label: 'node' },
      position: { x: 500, y: 500 },
      measured: { width: 50, height: 25 },
    };

    adoptUserNodes([userNode], nodeLookup, parentLookup, {
      nodeExtent,
    });

    const internalNode = nodeLookup.get('1');

    expect(internalNode?.internals.positionAbsolute.x).to.equal(nodeExtent[1][0] - userNode.measured!.width!);
    expect(internalNode?.internals.positionAbsolute.y).to.equal(nodeExtent[1][1] - userNode.measured!.height!);
  });

  it('calculates positionAbsolute for sub flow', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const userParentNode: Node = {
      id: '1',
      data: { label: 'node' },
      position: { x: 100, y: 100 },
      measured: { width: 100, height: 50 },
    };

    const userChildNode: Node = {
      id: '2',
      data: { label: 'child' },
      position: { x: 0, y: 0 },
      measured: { width: 100, height: 50 },
      parentId: '1',
    };

    adoptUserNodes([userParentNode, userChildNode], nodeLookup, parentLookup);

    const internalChildNode = nodeLookup.get('2');

    expect(nodeLookup.size).to.equal(2);

    expect(internalChildNode?.internals.positionAbsolute.x).to.equal(
      userChildNode.position.x + userParentNode.position.x
    );
    expect(internalChildNode?.internals.positionAbsolute.y).to.equal(
      userChildNode.position.y + userParentNode.position.y
    );
  });

  it('calculates positionAbsolute for sub flow with nodeOrigin', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();
    const nodeOrigin: NodeOrigin = [0.5, 0.5];

    const userParentNode: Node = {
      id: '1',
      data: { label: 'node' },
      position: { x: 100, y: 100 },
      measured: { width: 100, height: 50 },
    };

    const userChildNode: Node = {
      id: '2',
      data: { label: 'child' },
      position: { x: 0, y: 0 },
      measured: { width: 100, height: 50 },
      parentId: '1',
    };

    adoptUserNodes([userParentNode, userChildNode], nodeLookup, parentLookup, {
      nodeOrigin,
    });

    const internalParentNode = nodeLookup.get('1');
    const internalChildNode = nodeLookup.get('2');

    const expectedParentX = userParentNode.position.x - userParentNode.measured!.width! * nodeOrigin[0];
    const expectedParentY = userParentNode.position.y - userParentNode.measured!.height! * nodeOrigin[1];

    expect(internalParentNode?.internals.positionAbsolute.x).to.equal(expectedParentX);
    expect(internalParentNode?.internals.positionAbsolute.y).to.equal(expectedParentY);

    const expectedChildX = userChildNode.position.x - userChildNode.measured!.width! * nodeOrigin[0] + expectedParentX;
    const expectedChildY = userChildNode.position.y - userChildNode.measured!.height! * nodeOrigin[1] + expectedParentY;

    expect(internalChildNode?.internals.positionAbsolute.x).to.equal(expectedChildX);
    expect(internalChildNode?.internals.positionAbsolute.y).to.equal(expectedChildY);
  });

  it('calculates positionAbsolute for sub flow with nodeExtent', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();
    const nodeExtent: CoordinateExtent = [
      [0, 0],
      [200, 200],
    ];

    const userParentNodeA: Node = {
      id: 'a',
      data: { label: 'node' },
      position: { x: 500, y: 500 },
      measured: { width: 100, height: 50 },
    };

    const userParentNodeB: Node = {
      id: 'b',
      data: { label: 'node' },
      position: { x: 0, y: 0 },
      measured: { width: 100, height: 50 },
    };

    const userChildNode: Node = {
      id: 'c',
      data: { label: 'child' },
      position: { x: 1000, y: 1000 },
      measured: { width: 100, height: 50 },
      parentId: 'a',
    };

    adoptUserNodes([userParentNodeA, userParentNodeB, userChildNode], nodeLookup, parentLookup, {
      nodeExtent,
    });

    const internalParentNodeA = nodeLookup.get('a');
    const internalParentNodeB = nodeLookup.get('b');

    const internalChildNode = nodeLookup.get('c');

    const expectedParentX = nodeExtent[1][0] - userParentNodeA.measured!.width!;
    const expectedParentY = nodeExtent[1][1] - userParentNodeA.measured!.height!;

    // this node is inside the nodeExtent and should be restricted to it
    expect(internalParentNodeA?.internals.positionAbsolute.x).to.equal(expectedParentX);
    expect(internalParentNodeA?.internals.positionAbsolute.y).to.equal(expectedParentY);

    // this node is inside the nodeExtent and should not be affected by it
    expect(internalParentNodeB?.internals.positionAbsolute.x).to.equal(userParentNodeB.position.x);
    expect(internalParentNodeB?.internals.positionAbsolute.y).to.equal(userParentNodeB.position.y);

    const expectedChildX = nodeExtent[1][0] - userChildNode.measured!.width!;
    const expectedChildY = nodeExtent[1][1] - userChildNode.measured!.height!;

    expect(internalChildNode?.internals.positionAbsolute.x).to.equal(expectedChildX);
    expect(internalChildNode?.internals.positionAbsolute.y).to.equal(expectedChildY);
  });
});

export {};

```

## /examples/react/cypress/components/utils/apply-changes.cy.ts

```ts path="/examples/react/cypress/components/utils/apply-changes.cy.ts" 
import {
  applyNodeChanges,
  applyEdgeChanges,
  type Node,
  type Edge,
  NodeChange,
  EdgeChange,
  XYPosition,
} from '@xyflow/react';

const nodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
  },
  { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
  { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
  { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
];

const edges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e1-3', source: '1', target: '3' },
  { id: 'e2-3', source: '2', target: '3' },
];

describe('applyChanges Testing', () => {
  it('adds a node/edge', () => {
    const currentNodes: Node[] = [];
    const nodeChanges: NodeChange[] = [{ type: 'add', item: nodes[0] }];
    const nextNodes = applyNodeChanges(nodeChanges, currentNodes);
    expect(nextNodes.length).to.be.equal(nodeChanges.length);

    const currentEdges: Edge[] = [];
    const edgeChanges: EdgeChange[] = [{ type: 'add', item: edges[0] }];
    const nextEdges = applyEdgeChanges(edgeChanges, currentEdges);

    expect(nextEdges.length).to.be.equal(1);
  });

  it('adds multiple nodes/edges', () => {
    const currentNodes: Node[] = [];
    const nodeChanges: NodeChange[] = [
      { type: 'add', item: nodes[0] },
      { type: 'add', item: nodes[1] },
    ];
    const nextNodes = applyNodeChanges(nodeChanges, currentNodes);
    expect(nextNodes.length).to.be.equal(nodeChanges.length);

    const currentEdges: Edge[] = [];
    const edgeChanges: EdgeChange[] = [
      { type: 'add', item: edges[0] },
      { type: 'add', item: edges[1] },
    ];
    const nextEdges = applyEdgeChanges(edgeChanges, currentEdges);
    expect(nextEdges.length).to.be.equal(edgeChanges.length);
  });

  it('removes a node/edge', () => {
    const nodesLength = nodes.length;
    const nodeChanges: NodeChange[] = [{ type: 'remove', id: '1' }];
    const nextNodes = applyNodeChanges(nodeChanges, nodes);
    expect(nextNodes.length).to.be.equal(nodes.length - nodeChanges.length);
    expect(nodes.length).to.be.equal(nodesLength);

    const edgesLength = edges.length;
    const edgeChanges: EdgeChange[] = [{ type: 'remove', id: 'e1-2' }];
    const nextEdges = applyEdgeChanges(edgeChanges, edges);
    expect(nextEdges.length).to.be.equal(edges.length - 1);
    expect(edges.length).to.be.equal(edgesLength);
  });

  it('removes multiple node/edge', () => {
    const nodesLength = nodes.length;
    const nodeChanges: NodeChange[] = [
      { type: 'remove', id: '1' },
      { type: 'remove', id: '2' },
    ];
    const nextNodes = applyNodeChanges(nodeChanges, nodes);
    expect(nextNodes.length).to.be.equal(nodes.length - nodeChanges.length);
    expect(nodes.length).to.be.equal(nodesLength);

    const edgesLength = edges.length;
    const edgeChanges: EdgeChange[] = [
      { type: 'remove', id: 'e1-2' },
      { type: 'remove', id: 'e1-3' },
    ];
    const nextEdges = applyEdgeChanges(edgeChanges, edges);
    expect(nextEdges.length).to.be.equal(edges.length - edgeChanges.length);
    expect(edges.length).to.be.equal(edgesLength);
  });

  it('selects a node/edge', () => {
    const nodeChanges: NodeChange[] = [{ type: 'select', id: '1', selected: true }];
    const nextNodes = applyNodeChanges(nodeChanges, nodes);
    expect(nextNodes[0].selected).to.be.true;

    const edgeChanges: EdgeChange[] = [{ type: 'select', id: 'e1-2', selected: true }];
    const nextEdges = applyEdgeChanges(edgeChanges, edges);
    expect(nextEdges[0].selected).to.be.true;
  });

  it('deselects a node/edge', () => {
    const nodeChanges: NodeChange[] = [{ type: 'select', id: '1', selected: false }];
    const nextNodes = applyNodeChanges(
      nodeChanges,
      nodes.map((n) => ({ ...n, selected: true }))
    );
    expect(nextNodes[0].selected).to.be.false;
    expect(nextNodes[1].selected).to.be.true;

    const edgeChanges: EdgeChange[] = [{ type: 'select', id: 'e1-2', selected: false }];
    const nextEdges = applyEdgeChanges(
      edgeChanges,
      edges.map((e) => ({ ...e, selected: true }))
    );
    expect(nextEdges[0].selected).to.be.false;
    expect(nextEdges[1].selected).to.be.true;
  });

  it('updates node position', () => {
    const newPosition: XYPosition = { x: 100, y: 100 };
    const nodeChanges: NodeChange[] = [{ type: 'position', id: '1', position: newPosition }];
    const nextNodes = applyNodeChanges(nodeChanges, nodes);

    expect(nextNodes[0].position).to.be.deep.equal(newPosition);
  });

  it('updates node dimensions', () => {
    const newWidth = 200;
    const newHeight = 200;
    const nodeChanges: NodeChange[] = [
      { type: 'dimensions', id: '1', dimensions: { width: newWidth, height: newHeight } },
    ];
    const nextNodes = applyNodeChanges(nodeChanges, nodes);

    expect(nodes[0].measured).to.be.undefined;
    expect(nextNodes[0].measured).to.be.deep.equal({ width: newWidth, height: newHeight });
    expect(nextNodes[0].width).to.be.undefined;
    expect(nextNodes[0].height).to.be.undefined;
  });

  it('updates node position and dimensions', () => {
    const newPosition: XYPosition = { x: 100, y: 100 };
    const newWidth = 200;
    const newHeight = 200;
    const nodeChanges: NodeChange[] = [
      { type: 'position', id: '1', position: newPosition },
      { type: 'dimensions', id: '1', dimensions: { width: newWidth, height: newHeight } },
    ];
    const nextNodes = applyNodeChanges(nodeChanges, nodes);

    expect(nextNodes[0].position).to.be.deep.equal(newPosition);
    expect(nextNodes[0].measured).to.be.deep.equal({ width: newWidth, height: newHeight });
  });

  it('replaces nodes/edges', () => {
    const nodesLength = nodes.length;
    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(nodesLength);
    expect(nextNodes[0]).to.be.deep.equal(nodes[1]);

    const edgesLength = edges.length;
    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(edgesLength);
    expect(nextEdges[0]).to.be.deep.equal(edges[1]);
  });
});

export {};

```

## /examples/react/cypress/components/utils/get-nodes-bounds.cy.ts

```ts path="/examples/react/cypress/components/utils/get-nodes-bounds.cy.ts" 
import { adoptUserNodes, getNodesBounds, type NodeLookup, type ParentLookup } from '@xyflow/system';
import type { Node } from '@xyflow/react';

describe('getNodesBounds Testing', () => {
  it('calculates bounds for a node', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const nodes: Node[] = [
      {
        id: '1',
        data: { label: 'node' },
        position: { x: 10, y: 10 },
        measured: { width: 100, height: 50 },
      },
    ];

    adoptUserNodes(nodes, nodeLookup, parentLookup);

    const bounds = getNodesBounds(nodes, { nodeLookup });

    expect(bounds.x).to.equal(10);
    expect(bounds.y).to.equal(10);
    expect(bounds.width).to.equal(100);
    expect(bounds.height).to.equal(50);
  });

  it('calculates bounds for a node (-x, -y)', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const nodes: Node[] = [
      {
        id: '1',
        data: { label: 'node' },
        position: { x: -10, y: -10 },
        measured: { width: 100, height: 50 },
      },
    ];

    adoptUserNodes(nodes, nodeLookup, parentLookup);

    const bounds = getNodesBounds(nodes, { nodeLookup });

    expect(bounds.x).to.equal(-10);
    expect(bounds.y).to.equal(-10);
    expect(bounds.width).to.equal(100);
    expect(bounds.height).to.equal(50);
  });

  it('calculates bounds for multitple nodes', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const nodes: Node[] = [
      {
        id: '1',
        data: { label: 'node' },
        position: { x: -10, y: 10 },
        measured: { width: 100, height: 50 },
      },
      {
        id: '2',
        data: { label: 'node' },
        position: { x: 100, y: 100 },
        measured: { width: 100, height: 50 },
      },
    ];

    adoptUserNodes(nodes, nodeLookup, parentLookup);

    const bounds = getNodesBounds(nodes, { nodeLookup });

    expect(bounds.x).to.equal(-10);
    expect(bounds.y).to.equal(10);
    expect(bounds.width).to.equal(210);
    expect(bounds.height).to.equal(140);
  });

  it('calculates bounds for a node with a child node', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const nodes: Node[] = [
      {
        id: '1',
        data: { label: 'node' },
        position: { x: 10, y: 10 },
        measured: { width: 100, height: 50 },
      },
      {
        id: '2',
        data: { label: 'node' },
        position: { x: 200, y: 100 },
        measured: { width: 100, height: 50 },
        parentId: '1',
      },
      {
        id: '3',
        data: { label: 'node' },
        position: { x: 0, y: 200 },
        measured: { width: 100, height: 50 },
        parentId: '1',
      },
    ];

    adoptUserNodes(nodes, nodeLookup, parentLookup);

    const bounds = getNodesBounds(nodes, { nodeLookup });

    expect(bounds.x).to.equal(10);
    expect(bounds.y).to.equal(10);
    expect(bounds.width).to.equal(300);
    expect(bounds.height).to.equal(250);
  });

  it('calculates bounds for child nodes', () => {
    const nodeLookup: NodeLookup = new Map();
    const parentLookup: ParentLookup = new Map();

    const nodes: Node[] = [
      {
        id: '1',
        data: { label: 'node' },
        position: { x: 10, y: 10 },
        measured: { width: 100, height: 50 },
      },
      {
        id: '2',
        data: { label: 'node' },
        position: { x: 20, y: 20 },
        measured: { width: 100, height: 50 },
        parentId: '1',
      },
      {
        id: '3',
        data: { label: 'node' },
        position: { x: 20, y: 200 },
        measured: { width: 100, height: 50 },
        parentId: '1',
      },
    ];

    adoptUserNodes(nodes, nodeLookup, parentLookup);

    const bounds = getNodesBounds([nodes[1], nodes[2]], { nodeLookup });

    expect(bounds.x).to.equal(30);
    expect(bounds.y).to.equal(30);
    expect(bounds.width).to.equal(100);
    expect(bounds.height).to.equal(230);
  });
});

export {};

```

## /examples/react/cypress/components/utils/graph-utils.cy.ts

```ts path="/examples/react/cypress/components/utils/graph-utils.cy.ts" 
import { Node, Edge, isNode, isEdge, getOutgoers, getIncomers, addEdge } from '@xyflow/react';

const nodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
  },
  { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
  { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
  { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
];

const edges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e1-3', source: '1', target: '3' },
  { id: 'e2-3', source: '2', target: '3' },
];

describe('Graph Utils Testing', () => {
  it('tests isNode function', () => {
    expect(isNode(nodes[0])).to.be.true;
    expect(isNode(edges[0])).to.be.false;
  });

  it('tests isEdge function', () => {
    expect(isEdge(edges[0])).to.be.true;
    expect(isEdge(nodes[0])).to.be.false;
  });

  it('tests getOutgoers function', () => {
    const outgoers = getOutgoers(nodes[0], nodes, edges);

    expect(outgoers.length).to.be.equal(2);

    const noOutgoers = getOutgoers(nodes[2], nodes, edges);
    expect(noOutgoers.length).to.be.equal(0);
  });

  it('tests getIncomers function', () => {
    const incomers = getIncomers(nodes[2], nodes, edges);
    expect(incomers.length).to.be.equal(2);

    const noIncomers = getIncomers(nodes[0], nodes, edges);
    expect(noIncomers.length).to.be.equal(0);
  });

  describe('tests addEdge function', () => {
    it('adds edge', () => {
      const newEdge: Edge = { source: '1', target: '4', id: 'new-edge-1-4' };
      const nextEdges = addEdge(newEdge, edges);

      expect(nextEdges.length).to.be.equal(edges.length + 1);
    });

    it('tries to add existing edge', () => {
      const newEdge: Edge = { source: '2', target: '3', id: 'new-edge-2-3' };
      const nextEdges = addEdge(newEdge, edges);

      expect(nextEdges.length).to.be.equal(edges.length);
    });

    it('tries to add invalid edge', () => {
      // @ts-ignore
      const newEdge: Edge = { nosource: '1', notarget: '3' };

      try {
        addEdge(newEdge, edges);
      } catch (e: any) {
        console.log(e.message);

        expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target.");
      }
    });
  });
});

export {};

```

## /examples/react/cypress/e2e/basic.cy.ts

```ts path="/examples/react/cypress/e2e/basic.cy.ts" 
describe('Basic Flow Rendering', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/');
  });

  it('renders a flow with three nodes', () => {
    cy.get('.react-flow__renderer');
    cy.get('.react-flow-basic-example'); // check if className prop works
    cy.get('.react-flow__node').should('have.length', 4);
    cy.get('.react-flow__edge').should('have.length', 2);
    cy.get('.react-flow__node').children('.react-flow__handle');
  });

  it('renders a grid', () => {
    cy.get('.react-flow__background');
  });

  it('selects two nodes by clicks', () => {
    cy.get('body').type('{cmd}', { release: false });
    cy.get('.react-flow__node:first')
      .click()
      .should('have.class', 'selected')
      .get('.react-flow__node:last')
      .click()
      .should('have.class', 'selected')
      .get('.react-flow__node:first')
      .should('have.class', 'selected');
    cy.get('body').type('{cmd}', { release: true });
  });

  it('selects a node by click', () => {
    cy.get('.react-flow__node:first').as('node').click({ force: true }).should('have.class', 'selected');
  });

  it('deselects node', () => {
    cy.get('.react-flow__renderer').click('bottomLeft');
    cy.get('.react-flow__node:first').should('not.have.class', 'selected');
  });

  it('selects an edge by click', () => {
    cy.get('.react-flow__edge:first').as('edge').click({ force: true });
    cy.get('.react-flow__edge:first').should('have.class', 'selected');
  });

  it('deselects edge', () => {
    cy.get('.react-flow__renderer').click('bottomLeft');
    cy.get('.react-flow__edge:first').should('not.have.class', 'selected');
  });

  it('selects one node with a selection', () => {
    cy.get('body')
      .type('{Shift}', { release: false })
      .get('.react-flow__pane')
      .trigger('mousedown', 1, 10, { button: 0, force: true })
      .trigger('mousemove', 1000, 200, { button: 0 })
      .trigger('mouseup', 1000, 200, { button: 0 });

    cy.get('.react-flow__node').eq(0).should('have.class', 'selected');
    cy.get('.react-flow__node').eq(3).should('have.not.class', 'selected');

    cy.get('.react-flow__nodesselection-rect');
    cy.get('body').type('{shift}', { release: true, force: true });
  });

  it('selects all nodes', () => {
    cy.get('body')
      .type('{shift}', { release: false })
      .get('.react-flow__pane')
      .trigger('mousedown', 'topRight', { button: 0, force: true })
      .trigger('mousemove', 'bottomLeft', { button: 0 })
      .wait(50)
      .trigger('mouseup', 'bottomLeft', { button: 0, force: true })
      .wait(400)
      .get('.react-flow__node')
      .should('have.class', 'selected');

    cy.wait(200);
    cy.get('.react-flow__nodesselection-rect');

    cy.get('body').type('{shift}', { release: true, force: true });
  });

  it('removes selection', () => {
    cy.get('.react-flow__renderer').click('bottomLeft');
    cy.get('.react-flow__nodesselection-rect').should('not.exist');
  });

  it('selects an edge', () => {
    cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected');
  });

  it('drags a node', () => {
    const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');

    cy.drag('.react-flow__node:first', { x: 10, y: 10 }).then(($el: any) => {
      const styleAfterDrag = $el.css('transform');
      expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
    });
  });

  it('removes a node', () => {
    cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
    cy.get('html').realPress('Backspace');

    cy.get('.react-flow__node').should('have.length', 3);
    cy.get('.react-flow__edge').should('have.length', 0);
  });

  it('connects nodes', () => {
    cy.get('.react-flow__node')
      .contains('Node 3')
      .find('.react-flow__handle.source')
      .trigger('mousedown', { force: true, button: 0 });

    cy.get('.react-flow__node')
      .contains('Node 4')
      .find('.react-flow__handle.target')
      .trigger('mousemove', { force: true, button: 0 })
      .wait(200)
      .trigger('mouseup', { force: true, button: 0 });

    cy.get('.react-flow__edge').as('edge');
    cy.get('@edge').should('have.length', 1);
  });

  it('removes an edge', () => {
    cy.get('.react-flow__edge:first').click();
    cy.get('html').realPress('Backspace');

    cy.get('.react-flow__edge').should('have.length', 0);
  });

  it('drags the pane', () => {
    const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');

    // for d3 we have to pass the window to the event
    // https://github.com/cypress-io/cypress/issues/3441
    cy.window().then((win) => {
      cy.get('.react-flow__pane')
        .trigger('mousedown', 'topLeft', { button: 0, view: win })
        .trigger('mousemove', 'bottomLeft')
        .wait(50)
        .trigger('mouseup', { force: true, view: win })
        .then(() => {
          const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
          expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
        });
    });
  });

  it('zooms the pane', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');

    cy.get('.react-flow__pane')
      .trigger('wheel', 'topLeft', { deltaY: -200 })
      .wait(50)
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
        expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
      });
  });
});

export {};

```

## /examples/react/cypress/e2e/controls.cy.ts

```ts path="/examples/react/cypress/e2e/controls.cy.ts" 
describe('Controls Testing', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/');
  });

  it('renders the control panel', () => {
    cy.get('.react-flow__controls');
  });

  it('zooms in', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');

    cy.get('.react-flow__controls-zoomin')
      .click()
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
        expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
      });
  });

  it('zooms out', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');

    cy.get('.react-flow__controls-zoomout')
      .click()
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
        expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
      });
  });

  // view is already fitted so we drag the pane to un-fit it
  it('drags the pane', () => {
    const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');

    // for d3 we have to pass the window to the event
    // https://github.com/cypress-io/cypress/issues/3441
    cy.window().then((win) => {
      cy.get('.react-flow__renderer')
        .trigger('mousedown', 'topLeft', { button: 0, view: win })
        .trigger('mousemove', 10, 400)
        .wait(50)
        .trigger('mouseup', 10, 400, { force: true, view: win })
        .then(() => {
          const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
          expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
        });
    });
  });

  it('fits view', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');

    cy.get('.react-flow__controls-fitview')
      .click()
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
        expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
      });
  });

  it('uses interactive control - not interactive', () => {
    cy.get('.react-flow__node:first').click().should('have.class', 'selected');
    cy.get('.react-flow__pane').click('topLeft');
    cy.get('.react-flow__node:first').should('not.have.class', 'selected');

    cy.get('.react-flow__controls-interactive')
      .click()
      .then(() => {
        cy.get('.react-flow__node:first').should('not.have.class', 'selected');
      });
  });

  it('uses interactive control - interactive', () => {
    cy.get('.react-flow__controls-interactive')
      .click()
      .then(() => {
        cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
      });
  });
});

export {};

```

## /examples/react/cypress/e2e/draghandle.cy.ts

```ts path="/examples/react/cypress/e2e/draghandle.cy.ts" 
describe('DragHandle Flow Rendering', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/DragHandle');
  });

  it('renders a flow with a node', () => {
    cy.get('.react-flow__renderer');
    cy.get('.react-flow__node').should('have.length', 1);
  });

  it('tries to drag a node', () => {
    const $nodeElement = Cypress.$('.react-flow__node:first');
    const styleBeforeDrag = $nodeElement.css('transform');

    cy.drag('.react-flow__node:first', { x: 500, y: 500 }).then(() => {
      const styleAfterDrag = $nodeElement.css('transform');
      expect(styleBeforeDrag).to.be.equal(styleAfterDrag);
    });
  });

  it('drags a node', () => {
    const $nodeElement = Cypress.$('.react-flow__node:first');
    const styleBeforeDrag = $nodeElement.css('transform');

    cy.drag('.custom-drag-handle:first', { x: 500, y: 500 }).then(() => {
      const styleAfterDrag = $nodeElement.css('transform');
      expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
    });
  });
});

export {};

```

## /examples/react/cypress/e2e/empty.cy.ts

```ts path="/examples/react/cypress/e2e/empty.cy.ts" 
describe('Empty Flow Rendering', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/Empty');
  });

  it('renders an empty flow', () => {
    cy.get('.react-flow__renderer');
    cy.get('.react-flow__node').should('not.exist');
    cy.get('.react-flow__edge').should('not.exist');
  });

  it('renders empty selection', () => {
    cy.get('.react-flow__renderer').click();
    cy.get('body')
      .type('{shift}', { release: false })
      .wait(50)
      .get('.react-flow__pane')
      .trigger('mousedown', 400, 50, { button: 0, force: true })
      .trigger('mousemove', 200, 200, { button: 0 })
      .wait(50)
      .trigger('mouseup', 200, 200, { force: true });

    cy.get('body').type('{shift}', { release: true });
  });

  it('adds two nodes', () => {
    cy.contains('add node').click();
    cy.contains('add node').click();
  });

  it('connects nodes', () => {
    cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });

    cy.get('.react-flow__node')
      .last()
      .find('.react-flow__handle.target')
      .trigger('mousemove')
      .trigger('mouseup', { force: true });

    cy.get('.react-flow__edge').should('have.length', 1);
  });
});

export {};

```

## /examples/react/cypress/e2e/figma.cy.ts

```ts path="/examples/react/cypress/e2e/figma.cy.ts" 
describe('Figma Flow UI', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/figma');
  });

  it('renders a flow with three nodes', () => {
    cy.get('.react-flow__renderer');
    cy.get('.react-flow__node').should('have.length', 4);
    cy.get('.react-flow__edge').should('have.length', 2);
    cy.get('.react-flow__node').children('.react-flow__handle');
  });

  it('renders a grid', () => {
    cy.get('.react-flow__background');
  });

  it('selects all nodes by drag', () => {
    cy.window().then((win) => {
      cy.get('.react-flow__pane')
        .trigger('mousedown', 'topLeft', { button: 0, view: win })
        .trigger('mousemove', 'bottomRight', { force: true })
        .wait(50)
        .trigger('mouseup', { force: true, view: win })
        .then(() => {
          cy.get('.react-flow__node').should('have.class', 'selected');
        });
    });
  });

  it('removes selection', () => {
    cy.get('.react-flow__pane').click('topLeft');
    cy.get('.react-flow__node').should('not.have.class', 'selected');
  });

  it('drags using right click', () => {
    cy.window().then((win) => {
      cy.get('.react-flow__node:last').isWithinViewport();
      cy.get('.react-flow__pane')
        .trigger('mousedown', 'center', { button: 2, view: win })
        .trigger('mousemove', 'bottom', { force: true })
        .wait(50)
        .trigger('mouseup', { force: true, view: win })
        .then(() => {
          cy.get('.react-flow__node').should('not.have.class', 'selected');
          cy.get('.react-flow__node:last').isOutsideViewport();
        });
    });
  });
});

export {};

```

## /examples/react/cypress/e2e/hidden.cy.ts

```ts path="/examples/react/cypress/e2e/hidden.cy.ts" 
describe('Hidden Flow Rendering', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/Hidden');
  });

  it('renders empty flow', () => {
    cy.get('.react-flow__node').should('not.exist');
    cy.get('.react-flow__edge').should('not.exist');
    cy.get('.react-flow__minimap-node').should('not.exist');
  });

  it('toggles isHidden mode', () => {
    cy.get('.react-flow__ishidden').click();
  });

  it('renders initial flow', () => {
    cy.get('.react-flow__renderer');
    cy.get('.react-flow__node').should('have.length', 4);
    cy.get('.react-flow__edge').should('have.length', 3);
    cy.get('.react-flow__minimap-node').should('have.length', 4);
  });

  it('toggles isHidden mode again', () => {
    cy.get('.react-flow__ishidden').click();
  });

  it('renders empty flow', () => {
    cy.get('.react-flow__node').should('not.exist');
    cy.get('.react-flow__edge').should('not.exist');
    cy.get('.react-flow__minimap-node').should('not.exist');
  });
});

export {};

```

## /examples/react/cypress/e2e/interaction.cy.ts

```ts path="/examples/react/cypress/e2e/interaction.cy.ts" 
describe('Interaction Flow Rendering', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/Interaction');
  });

  it('renders initial flow', () => {
    cy.get('.react-flow__renderer');
    cy.get('.react-flow__node').should('have.length', 4);
    cy.get('.react-flow__edge').should('have.length', 2);
    cy.get('.react-flow__node').children('.react-flow__handle');
  });

  it('tries to select a node by click', () => {
    const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
    expect(pointerEvents).to.equal('none');
  });

  it('tries to select an edge by click', () => {
    const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
    expect(pointerEvents).to.equal('none');
  });

  it('toggles on capture element click', () => {
    cy.get('.react-flow__captureelementclick').click();
  });

  it('allows node clicks when enabled', () => {
    const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
    expect(pointerEvents).to.equal('all');
  });

  it('allows edge clicks when enabled', () => {
    const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
    expect(pointerEvents.toLowerCase()).to.equal('visiblestroke');
  });

  it('tries to do a selection', () => {
    cy.get('body')
      .type('{shift}', { release: false })
      .wait(50)
      .get('.react-flow__pane')
      .trigger('mousedown', 1000, 50, { button: 0, force: true })
      .trigger('mousemove', 1, 400, { button: 0 })
      .wait(50)
      .get('.react-flow__selection')
      .should('not.exist');

    cy.get('.react-flow__pane').trigger('mouseup', 1, 200, { force: true });

    cy.get('body').type('{shift}', { release: true });
  });

  it('tries to connect to nodes', () => {
    cy.get('.react-flow__node')
      .contains('Node 3')
      .find('.react-flow__handle.source')
      .then(($el) => {
        const pointerEvents = $el.css('pointer-events');
        expect(pointerEvents).to.equal('none');
      });
  });

  it('tries to zoom by double click', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');

    cy.get('.react-flow__renderer')
      .dblclick()
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
        expect(styleBeforeZoom).to.equal(styleAfterZoom);
      });
  });

  it('tries to zoom by scroll', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');

    cy.get('.react-flow__renderer')
      .trigger('wheel', 'topLeft', { deltaY: -200 })
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
        expect(styleBeforeZoom).to.equal(styleAfterZoom);
      });
  });

  it('toggles draggable mode', () => {
    cy.get('.react-flow__draggable').click();
  });

  it('drags a node', () => {
    const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');

    cy.drag('.react-flow__node:first', { x: 325, y: 100 }).then(($el) => {
      const styleAfterDrag = $el.css('transform');
      expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
    });
  });

  it('toggles selectable mode', () => {
    cy.get('.react-flow__selectable').click();
  });

  it('selects a node by click', () => {
    cy.get('.react-flow__node:first').click().should('have.class', 'selected');
  });

  it('selects an edge by click', () => {
    cy.get('.react-flow__edge:first').click({ force: true });
    cy.get('.react-flow__edge:first').should('have.class', 'selected');
  });

  it('toggles connectable mode', () => {
    cy.get('.react-flow__connectable').click();
  });

  it('connects two nodes', () => {
    cy.get('.react-flow__node')
      .contains('Node 3')
      .find('.react-flow__handle.source')
      .trigger('mousedown', { button: 0 });

    cy.get('.react-flow__node')
      .contains('Node 4')
      .find('.react-flow__handle.target')
      .trigger('mousemove')
      .trigger('mouseup', { force: true });

    cy.get('.react-flow__edge').should('have.length', 3);
  });

  it('toggles zoom on scroll', () => {
    cy.get('.react-flow__zoomonscroll').click();
  });

  it('zooms by scroll', () => {
    const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');

    cy.get('.react-flow__pane')
      .trigger('wheel', 'topLeft', { deltaY: 200 })
      .wait(50)
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
        expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
      });
  });

  it('toggles zoom on double click', () => {
    cy.get('.react-flow__zoomondbl').click();
  });

  it('zooms by double click', () => {
    cy.get('.react-flow__controls-zoomout').click();
    const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');

    cy.get('.react-flow__pane')
      .dblclick()
      .wait(50)
      .then(() => {
        const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
        expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
      });
  });
});

export {};

```

## /examples/react/cypress/e2e/minimap.cy.ts

```ts path="/examples/react/cypress/e2e/minimap.cy.ts" 
describe('Minimap Testing', { testIsolation: false }, () => {
  before(() => {
    cy.visit('/');
  });

  it('renders the mini map', () => {
    cy.get('.react-flow__minimap');
    cy.get('.react-flow__minimap-mask');
  });

  it('has same number of nodes as the pane', () => {
    cy.get('.react-flow__minimap-node').then(() => {
      const paneNodes = Cypress.$('.react-flow__node').length;
      const minimapNodes = Cypress.$('.react-flow__minimap-node').length;

      expect(paneNodes).equal(minimapNodes);
    });
  });

  it('changes zoom level', () => {
    const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox');
    const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d');

    cy.get('.react-flow__pane')
      .trigger('wheel', 'topLeft', { deltaY: -200 })
      .wait(50)
      .then(() => {
        const viewBoxAfterZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox');
        const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');

        expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
        expect(maskPathBeforeZoom).to.not.equal(maskPathAfterZoom);
      });
  });

  it('changes node position', () => {
    const minimapNode = Cypress.$('.react-flow__minimap-node:first');

    const xPosBeforeDrag = Number(minimapNode.attr('x'));
    const yPosBeforeDrag = Number(minimapNode.attr('y'));

    cy.drag('.react-flow__node:first', { x: 500, y: 25 })
      .wait(100)
      .then(() => {
        const xPosAfterDrag = Number(minimapNode.attr('x'));
        const yPosAfterDrag = Number(minimapNode.attr('y'));

        expect(xPosAfterDrag).not.to.equal(xPosBeforeDrag);
        expect(yPosAfterDrag).not.to.equal(yPosBeforeDrag);
        expect(xPosAfterDrag - xPosBeforeDrag).to.be.greaterThan(yPosAfterDrag - yPosBeforeDrag);
      });
  });

  it('changes node positions via pane drag', () => {
    const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox');
    const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d');

    // for d3 we have to pass the window to the event
    // https://github.com/cypress-io/cypress/issues/3441
    cy.window().then((win) => {
      cy.get('.react-flow__pane')
        .trigger('mousedown', 'topLeft', { button: 0, view: win })
        .trigger('mousemove', 'bottomLeft')
        .wait(50)
        .trigger('mouseup', { force: true, view: win })
        .then(() => {
          const viewBoxAfterDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox');
          const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');

          expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag);
          expect(maskPathBeforeDrag).to.not.equal(maskPathAfterDrag);
        });
    });
  });
});

export {};

```

## /examples/react/cypress/fixtures/simpleflow.ts

```ts path="/examples/react/cypress/fixtures/simpleflow.ts" 
import { Node, Edge } from '@xyflow/react';

export const nodes: Node[] = [
  {
    id: '1',
    data: { label: 'Node 1' },
    position: { x: 0, y: 0 },
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 200, y: 200 },
  },
];

export const edges: Edge[] = [
  {
    id: 'e1',
    source: '1',
    target: '2',
  },
];

```

## /examples/react/cypress/support/ControlledFlow.tsx

```tsx path="/examples/react/cypress/support/ControlledFlow.tsx" 
import { useCallback, useState } from 'react';
import {
  ReactFlow,
  Node,
  Edge,
  NodeChange,
  EdgeChange,
  applyNodeChanges,
  applyEdgeChanges,
  Connection,
  addEdge,
  ReactFlowProps,
} from '@xyflow/react';

function ControlledFlow({
  addOnNodeChangeHandler = true,
  addOnEdgeChangeHandler = true,
  addOnConnectHandler = true,
  initialNodes = [],
  initialEdges = [],
  ...rest
}: {
  initialNodes?: Node[];
  initialEdges?: Edge[];
  addOnNodeChangeHandler?: boolean;
  addOnEdgeChangeHandler?: boolean;
  addOnConnectHandler?: boolean;
} & Partial<ReactFlowProps>) {
  const [nodes, setNodes] = useState<Node[]>(initialNodes);
  const [edges, setEdges] = useState<Edge[]>(initialEdges);

  const onNodesChange = useCallback(
    (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)),
    [setNodes]
  );

  const onEdgesChange = useCallback(
    (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),
    [setEdges]
  );

  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  const handlers: {
    onNodesChange?: (changes: NodeChange[]) => void;
    onEdgesChange?: (changes: EdgeChange[]) => void;
    onConnect?: (params: Connection | Edge) => void;
  } = {};

  if (addOnNodeChangeHandler) {
    handlers.onNodesChange = onNodesChange;
  }

  if (addOnEdgeChangeHandler) {
    handlers.onEdgesChange = onEdgesChange;
  }

  if (addOnConnectHandler) {
    handlers.onConnect = onConnect;
  }

  return <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} nodeDragThreshold={0} />;
}

export default ControlledFlow;

```

## /examples/react/cypress/support/commands.ts

```ts path="/examples/react/cypress/support/commands.ts" 
Cypress.Commands.add('drag', (selector, { x, y }) =>
  cy.window().then((window) => {
    const elementToDrag = cy.get(selector as string);
    return elementToDrag.then(($el) => {
      const { left, top, width, height } = $el[0].getBoundingClientRect();
      const centerX = left + width / 2;
      const centerY = top + height / 2;
      const nextX: number = centerX + x;
      const nextY: number = centerY + y;

      return elementToDrag
        .trigger('mousedown', { view: window, force: true })
        .trigger('mousemove', nextX, nextY, { force: true })
        .wait(50)
        .trigger('mouseup', { view: window, force: true });
    });
  })
);

Cypress.Commands.add('dragPane', ({ from, to }) =>
  cy
    .window()
    .then((window) =>
      cy
        .get('.react-flow__pane')
        .trigger('mousedown', from.x, from.y, { view: window })
        .trigger('mousemove', to.x, to.y)
        .trigger('mouseup', { force: true, view: window })
    )
);

Cypress.Commands.add('zoomPane', (wheelDelta: number) =>
  cy.get('.react-flow__pane').trigger('wheel', 'center', { deltaY: wheelDelta }).wait(250)
);

Cypress.Commands.add('isWithinViewport', { prevSubject: true }, (subject) => {
  const rect = subject[0].getBoundingClientRect();

  return cy.window().then((window) => {
    expect(rect.top).to.be.within(0, window.innerHeight);
    expect(rect.right).to.be.within(0, window.innerWidth);
    expect(rect.bottom).to.be.within(0, window.innerHeight);
    expect(rect.left).to.be.within(0, window.innerWidth);

    return subject;
  });
});

Cypress.Commands.add('isOutsideViewport', { prevSubject: true }, (subject) => {
  const rect = subject[0].getBoundingClientRect();

  return cy.window().then((window) => {
    expect(window.innerHeight < rect.top || rect.bottom < 0 || window.innerWidth < rect.left || rect.right < 0).to.be
      .true;

    return subject;
  });
});

export {};

```

## /examples/react/cypress/support/component-index.html

```html path="/examples/react/cypress/support/component-index.html" 
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title>Components App</title>
    <style>
      html,
      body,
      #root {
        margin: 0;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <div data-cy-root id="root"></div>
  </body>
</html>

```

## /examples/react/cypress/support/component.ts

```ts path="/examples/react/cypress/support/component.ts" 
///<reference types="cypress" />
///<reference types="@cypress/skip-test" />
///<reference types="cypress-real-events" />

import './commands';
import 'cypress-real-events/support';

import { mount } from 'cypress/react18';
import { XYPosition } from '@xyflow/react';

import '@xyflow/react/dist/style.css';

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace Cypress {
    interface Chainable {
      mount: typeof mount;
      drag: (selector: string, toPosition: XYPosition) => Cypress.Chainable<JQuery<HTMLElement>>;
      dragPane: ({ from, to }: { from: XYPosition; to: XYPosition }) => Cypress.Chainable<JQuery<HTMLElement>>;
      zoomPane: (wheelDelta: number) => Cypress.Chainable<JQuery<HTMLElement>>;
      isWithinViewport: () => Cypress.Chainable<JQuery<HTMLElement>>;
      isOutsideViewport: () => Cypress.Chainable<JQuery<HTMLElement>>;
    }
  }
}

Cypress.Commands.add('mount', mount);

```

## /examples/react/cypress/support/e2e.ts

```ts path="/examples/react/cypress/support/e2e.ts" 
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands';
import 'cypress-real-events/support';

const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/;

Cypress.on('uncaught:exception', (err) => {
  if (resizeObserverLoopErrRe.test(err.message)) {
    return false;
  }
});

```

## /examples/react/index.html

```html path="/examples/react/index.html" 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React Flow Examples</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

## /examples/react/package.json

```json path="/examples/react/package.json" 
{
  "name": "react-examples",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite --port 3000 --open --host",
    "serve": "vite serve --port 3000",
    "build": "vite build",
    "preview": "vite preview",
    "test:dev": "cypress open",
    "test": "pnpm test-component && pnpm test-e2e",
    "test-component": "cypress run --component",
    "test-e2e-cypress": "cypress run --e2e --headless",
    "test-e2e": "start-server-and-test 'pnpm serve' http-get://localhost:3000 'pnpm test-e2e-cypress'"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^2.2.3",
    "@xyflow/react": "workspace:*",
    "@xyflow/system": "workspace:*",
    "classcat": "^5.0.4",
    "dagre": "^0.8.5",
    "localforage": "^1.10.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-redux": "^9.1.1",
    "react-router-dom": "^6.18.0",
    "redux": "^5.0.1",
    "zustand": "^4.4.6"
  },
  "devDependencies": {
    "@cypress/skip-test": "^2.6.1",
    "@types/dagre": "^0.7.52",
    "@types/react": "^18.2.36",
    "@types/react-dom": "^18.2.14",
    "@vitejs/plugin-react": "4.1.1",
    "@vitejs/plugin-react-swc": "^3.4.1",
    "cypress": "13.6.6",
    "cypress-real-events": "1.12.0",
    "start-server-and-test": "^2.0.2",
    "typescript": "5.4.5",
    "vite": "4.5.0"
  }
}

```

## /examples/react/public/favicon.ico

Binary file available at https://raw.githubusercontent.com/xyflow/xyflow/refs/heads/main/examples/react/public/favicon.ico

## /examples/react/src/App/header.tsx

```tsx path="/examples/react/src/App/header.tsx" 
import { useEffect, useState } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';

import routes from './routes';

export default function Header() {
  const location = useLocation();
  const navigate = useNavigate();

  const pathParts = location.pathname.split('/').filter(Boolean);
  const initialExample = pathParts.length > 1 ? pathParts[1] : 'basic';

  const [currentPath, setCurrentPath] = useState(initialExample);

  useEffect(() => {
    const name = routes.find((route) => route.path === currentPath)?.name;
    document.title = `React Flow Examples${name ? ' - ' + name : ''}`;
    navigate(currentPath);
  }, [currentPath, navigate]);

  return (
    <>
      <header>
        <a className="logo" href="https://github.com/xyflow/xyflow">
          React Flow Dev
        </a>
        <select
          value={currentPath}
          onChange={(event) => setCurrentPath(event.target.value)}
          aria-label="select an example"
        >
          {routes.map((route) => (
            <option value={route.path} key={route.path}>
              {route.name}
            </option>
          ))}
        </select>
      </header>
      <Outlet />
    </>
  );
}

```

## /examples/react/src/App/index.tsx

```tsx path="/examples/react/src/App/index.tsx" 
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';

import routes from './routes';
import GenericTestRoutes from '../generic-tests';
import Basic from '../examples/Basic';
import Header from './header';

export default () => (
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<Navigate to="/examples" />} />
      <Route path="examples" element={<Header />}>
        <Route index element={<Basic />} />
        {routes.map((route) => (
          <Route path={route.path} key={route.path} element={<route.component />} />
        ))}
      </Route>
      <Route path="tests/generic/*" element={<GenericTestRoutes />} />
    </Routes>
  </BrowserRouter>
);

```

## /examples/react/src/App/routes.ts

```ts path="/examples/react/src/App/routes.ts" 
import A11y from '../examples/A11y';
import Basic from '../examples/Basic';
import Backgrounds from '../examples/Backgrounds';
import BrokenNodes from '../examples/BrokenNodes';
import ColorMode from '../examples/ColorMode';
import ClickDistance from '../examples/ClickDistance';
import ControlledUncontrolled from '../examples/ControlledUncontrolled';
import ControlledViewport from '../examples/ControlledViewport';
import CustomConnectionLine from '../examples/CustomConnectionLine';
import CustomMiniMapNode from '../examples/CustomMiniMapNode';
import CustomNode from '../examples/CustomNode';
import DefaultEdgeOverwrite from '../examples/DefaultEdgeOverwrite';
import DefaultNodeOverwrite from '../examples/DefaultNodeOverwrite';
import DefaultNodes from '../examples/DefaultNodes';
import DragHandle from '../examples/DragHandle';
import DragNDrop from '../examples/DragNDrop';
import EasyConnect from '../examples/EasyConnect';
import Edges from '../examples/Edges';
import EdgeRenderer from '../examples/EdgeRenderer';
import EdgeTypes from '../examples/EdgeTypes';
import Empty from '../examples/Empty';
import Figma from '../examples/Figma';
import FloatingEdges from '../examples/FloatingEdges';
import Hidden from '../examples/Hidden';
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';
import Overview from '../examples/Overview';
import Provider from '../examples/Provider';
import SaveRestore from '../examples/SaveRestore';
import SetNodesBatching from '../examples/SetNodesBatching';
import Stress from '../examples/Stress';
import Subflow from '../examples/Subflow';
import SwitchFlow from '../examples/Switch';
import TouchDevice from '../examples/TouchDevice';
import Undirectional from '../examples/Undirectional';
import ReconnectEdge from '../examples/ReconnectEdge';
import UpdateNode from '../examples/UpdateNode';
import UseUpdateNodeInternals from '../examples/UseUpdateNodeInternals';
import UseReactFlow from '../examples/UseReactFlow';
import Validation from '../examples/Validation';
import UseKeyPress from '../examples/UseKeyPress';
import EdgeRouting from '../examples/EdgeRouting';
import CancelConnection from '../examples/CancelConnection';
import InteractiveMinimap from '../examples/InteractiveMinimap';
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
import NodeToolbar from '../examples/NodeToolbar';
import EdgeToolbar from '../examples/EdgeToolbar';
import UseConnection from '../examples/UseConnection';
import UseNodesInitialized from '../examples/UseNodesInit';
import UseNodesData from '../examples/UseNodesData';
import UseNodeConnections from '../examples/UseNodeConnections';
import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop';
import DevTools from '../examples/DevTools';
import Redux from '../examples/Redux';
import MovingHandles from '../examples/MovingHandles';
import DetachedHandle from '../examples/DetachedHandle';

export interface IRoute {
  name: string;
  path: string;
  component: React.ComponentType;
}

const routes: IRoute[] = [
  {
    name: 'Add Node on edge Drop',
    path: 'add-node-edge-drop',
    component: AddNodeOnEdgeDrop,
  },
  {
    name: 'A11y',
    path: 'a11y',
    component: A11y,
  },
  {
    name: 'Basic',
    path: 'basic',
    component: Basic,
  },
  {
    name: 'Backgrounds',
    path: 'backgrounds',
    component: Backgrounds,
  },
  {
    name: 'Broken Nodes',
    path: 'broken-nodes',
    component: BrokenNodes,
  },
  {
    name: 'Color Mode',
    path: 'color-mode',
    component: ColorMode,
  },
  {
    name: 'Cancel Connection',
    path: 'cancel-connection',
    component: CancelConnection,
  },
  {
    name: 'Click Distance',
    path: 'click-distance',
    component: ClickDistance,
  },
  {
    name: 'Controlled/Uncontrolled',
    path: 'controlled-uncontrolled',
    component: ControlledUncontrolled,
  },
  {
    name: 'Controlled Viewport',
    path: 'controlled-viewport',
    component: ControlledViewport,
  },
  {
    name: 'Custom Connection Line',
    path: 'custom-connectionline',
    component: CustomConnectionLine,
  },
  {
    name: 'Custom Minimap Node',
    path: 'custom-minimap-node',
    component: CustomMiniMapNode,
  },
  {
    name: 'Custom Node',
    path: 'custom-node',
    component: CustomNode,
  },
  {
    name: 'Default Node Overwrite',
    path: 'default-node-overwrite',
    component: DefaultNodeOverwrite,
  },
  {
    name: 'Default Edge Overwrite',
    path: 'default-edge-overwrite',
    component: DefaultEdgeOverwrite,
  },
  {
    name: 'Default Nodes',
    path: 'default-nodes',
    component: DefaultNodes,
  },
  {
    name: 'DetachedHandle',
    path: 'detached-handle',
    component: DetachedHandle,
  },
  {
    name: 'DevTools',
    path: 'devtools',
    component: DevTools,
  },
  {
    name: 'Drag Handle',
    path: 'draghandle',
    component: DragHandle,
  },
  {
    name: 'Drag and Drop',
    path: 'dragndrop',
    component: DragNDrop,
  },
  {
    name: 'EasyConnect',
    path: 'easy-connect',
    component: EasyConnect,
  },
  {
    name: 'Edges',
    path: 'edges',
    component: Edges,
  },
  {
    name: 'Edge Renderer',
    path: 'edge-renderer',
    component: EdgeRenderer,
  },
  {
    name: 'Edge Types',
    path: 'edge-types',
    component: EdgeTypes,
  },
  {
    name: 'Edge Routing',
    path: 'edge-routing',
    component: EdgeRouting,
  },
  {
    name: 'Edge Toolbar',
    path: 'edge-toolbar',
    component: EdgeToolbar,
  },
  {
    name: 'Empty',
    path: 'empty',
    component: Empty,
  },
  {
    name: 'Figma',
    path: 'figma',
    component: Figma,
  },
  {
    name: 'Floating Edges',
    path: 'floating-edges',
    component: FloatingEdges,
  },
  {
    name: 'Hidden',
    path: 'hidden',
    component: Hidden,
  },
  {
    name: 'Interaction',
    path: 'interaction',
    component: Interaction,
  },
  {
    name: 'Intersection',
    path: 'intersection',
    component: Intersection,
  },
  {
    name: 'Interactive Minimap',
    path: 'interactive-minimap',
    component: InteractiveMinimap,
  },
  {
    name: 'Layouting',
    path: 'layouting',
    component: Layouting,
  },
  {
    name: 'Multi setNodes',
    path: 'multi-setnodes',
    component: MultiSetNodes,
  },
  {
    name: 'Moving Handles',
    path: 'moving-handles',
    component: MovingHandles,
  },
  {
    name: 'Multi Flows',
    path: 'multiflows',
    component: MultiFlows,
  },
  {
    name: 'Node Type Change',
    path: 'nodetype-change',
    component: NodeTypeChange,
  },
  {
    name: 'nodeTypes Object Change',
    path: 'nodetypesobject-change',
    component: NodeTypesObjectChange,
  },
  {
    name: 'NodeToolbar',
    path: 'node-toolbar',
    component: NodeToolbar,
  },
  {
    name: 'NodeResizer',
    path: 'node-resizer',
    component: NodeResizer,
  },
  {
    name: 'Overview',
    path: 'overview',
    component: Overview,
  },
  {
    name: 'Provider',
    path: 'provider',
    component: Provider,
  },
  {
    name: 'Save/Restore',
    path: 'save-restore',
    component: SaveRestore,
  },
  {
    name: 'SetNodes Batching',
    path: 'setnodes-batching',
    component: SetNodesBatching,
  },
  {
    name: 'Stress',
    path: 'stress',
    component: Stress,
  },
  {
    name: 'Subflow',
    path: 'subflow',
    component: Subflow,
  },
  {
    name: 'Switch Flow',
    path: 'switch',
    component: SwitchFlow,
  },
  {
    name: 'Touch Device',
    path: 'touch-device',
    component: TouchDevice,
  },
  {
    name: 'Undirectional',
    path: 'undirectional',
    component: Undirectional,
  },
  {
    name: 'Reconnect Edge',
    path: 'reconnect-edge',
    component: ReconnectEdge,
  },
  {
    name: 'Update Node',
    path: 'update-node',
    component: UpdateNode,
  },
  {
    name: 'useConnection',
    path: 'use-connection',
    component: UseConnection,
  },
  {
    name: 'useNodesInitialized',
    path: 'use-nodes-initialized',
    component: UseNodesInitialized,
  },
  {
    name: 'useOnSelectionChange',
    path: 'use-on-selection-change',
    component: UseOnSelectionChange,
  },
  {
    name: 'useReactFlow',
    path: 'usereactflow',
    component: UseReactFlow,
  },
  {
    name: 'useNodeConnections',
    path: 'usenodeconnections',
    component: UseNodeConnections,
  },
  {
    name: 'useNodesData',
    path: 'usenodesdata',
    component: UseNodesData,
  },
  {
    name: 'useUpdateNodeInternals',
    path: 'useupdatenodeinternals',
    component: UseUpdateNodeInternals,
  },
  {
    name: 'redux',
    path: 'redux',
    component: Redux,
  },
  {
    name: 'Validation',
    path: 'validation',
    component: Validation,
  },
  {
    name: 'useKeyPress',
    path: 'use-key-press',
    component: UseKeyPress,
  },
];

export default routes;

```

## /examples/react/src/examples/A11y/index.tsx

```tsx path="/examples/react/src/examples/A11y/index.tsx" 
import { useState } from 'react';
import {
  ReactFlow,
  MiniMap,
  Background,
  BackgroundVariant,
  Controls,
  ReactFlowProvider,
  Node,
  Edge,
  AriaLabelConfig,
  Panel,
} from '@xyflow/react';

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'A11y Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
    domAttributes: {
      tabIndex: 10,
      'aria-roledescription': 'A11y Node',
    },
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 1000, y: 100 },
  },
  {
    id: '3',
    data: { label: 'Node 3' },
    position: { x: 100, y: 100 },
    className: 'light',
    ariaRole: 'button',
  },
  {
    id: '4',
    data: { label: 'Node 4' },
    position: { x: 300, y: 100 },
  },
  {
    id: '5',
    data: { label: 'Node 5' },
    position: { x: 400, y: 200 },
  },
  {
    id: '6',
    data: { label: 'Node 6' },
    position: { x: -1000, y: 200 },
  },
];

const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e1-3', source: '1', target: '3' },
  { id: 'e1-4', source: '1', target: '4' },
  { id: 'e1-5', source: '4', target: '5' },
  { id: 'e1-6', source: '3', target: '6' },
];

const ariaLabelConfig: Partial<AriaLabelConfig> = {
  'node.a11yDescription.default': 'Custom Node Desc.',
  'node.a11yDescription.keyboardDisabled': 'Custom Keyboard Desc.',
  'node.a11yDescription.ariaLiveMessage': ({ direction, x, y }) =>
    `Custom Moved selected node ${direction}. New position, x: ${x}, y: ${y}`,
  'edge.a11yDescription.default': 'Custom Edge Desc.',
  'controls.ariaLabel': 'Custom Controls Aria Label',
  'controls.zoomIn.ariaLabel': 'Custom Zoom in',
  'controls.zoomOut.ariaLabel': 'Custom Zoom Out',
  'controls.fitView.ariaLabel': 'Custom Fit View',
  'controls.interactive.ariaLabel': 'Custom Toggle Interactivity',
  'minimap.ariaLabel': 'Custom Aria Label',
};

const A11y = () => {
  const [autoPanOnNodeFocus, setAutoPanOnNodeFocus] = useState(true);

  return (
    <ReactFlow
      defaultNodes={initialNodes}
      defaultEdges={initialEdges}
      autoPanOnNodeFocus={autoPanOnNodeFocus}
      selectNodesOnDrag={false}
      elevateEdgesOnSelect
      elevateNodesOnSelect={false}
      nodeDragThreshold={0}
      ariaLabelConfig={ariaLabelConfig}
    >
      <Background variant={BackgroundVariant.Dots} />
      <MiniMap />
      <Controls />
      <Panel position="top-right">
        <div>
          <label htmlFor="focusPannable">
            <input
              id="focusPannable"
              type="checkbox"
              checked={autoPanOnNodeFocus}
              onChange={(event) => setAutoPanOnNodeFocus(event.target.checked)}
              className="xy-theme__checkbox"
            />
            autoPanOnNodeFocus
          </label>
        </div>
      </Panel>
    </ReactFlow>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <A11y />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx

```tsx path="/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx" 
import { useCallback, useRef } from 'react';
import {
  ReactFlow,
  useNodesState,
  useEdgesState,
  addEdge,
  useReactFlow,
  ReactFlowProvider,
  OnConnect,
  OnConnectStart,
  OnConnectEnd,
  Node,
  Edge,
} from '@xyflow/react';

const initialNodes: Node[] = [
  {
    id: '0',
    type: 'input',
    data: { label: 'Node' },
    position: { x: 0, y: 50 },
  },
];

const initialEdges: Edge[] = [];

let id = 1;
const getId = () => `${id++}`;

const AddNodeOnEdgeDrop = () => {
  const reactFlowWrapper = useRef(null);
  const connectingNodeId = useRef<string | null>(null);
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const { screenToFlowPosition } = useReactFlow();
  const onConnect: OnConnect = useCallback((params) => {
    // reset the start node on connections
    connectingNodeId.current = null;
    setEdges((eds) => addEdge(params, eds));
  }, []);

  const onConnectStart: OnConnectStart = useCallback((_, { nodeId }) => {
    connectingNodeId.current = nodeId;
  }, []);

  const onConnectEnd: OnConnectEnd = useCallback(
    (event) => {
      if (!connectingNodeId.current) return;

      const targetIsPane = (event.target as Partial<Element> | null)?.classList?.contains('react-flow__pane');

      if (targetIsPane && 'clientX' in event && 'clientY' in event) {
        // we need to remove the wrapper bounds, in order to get the correct position
        const id = getId();
        const newNode: Node = {
          id,
          position: screenToFlowPosition({
            x: event.clientX,
            y: event.clientY,
          }),
          data: { label: `Node ${id}` },
          origin: [0.5, 0.0],
        };

        const newEdge: Edge = {
          id,
          source: connectingNodeId.current,
          target: id,
        };

        setNodes((nds) => nds.concat(newNode));
        setEdges((eds) => eds.concat(newEdge));
      }
    },
    [screenToFlowPosition]
  );

  return (
    <div className="wrapper" ref={reactFlowWrapper} style={{ height: '100%' }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        onConnectStart={onConnectStart}
        onConnectEnd={onConnectEnd}
        fitView
      />
    </div>
  );
};

export default () => (
  <ReactFlowProvider>
    <AddNodeOnEdgeDrop />
  </ReactFlowProvider>
);

```

## /examples/react/src/examples/Backgrounds/index.tsx

```tsx path="/examples/react/src/examples/Backgrounds/index.tsx" 
import { FC } from 'react';

import {
  ReactFlow,
  Node,
  ReactFlowProvider,
  useNodesState,
  Background,
  BackgroundProps,
  BackgroundVariant,
} from '@xyflow/react';

import styles from './style.module.css';

const initialNodes: Node[] = [
  {
    id: '1',
    data: { label: 'Node 1' },
    position: { x: 50, y: 50 },
  },
];

const Flow: FC<{ id: string; bgProps: BackgroundProps[] }> = ({ id, bgProps }) => {
  const [nodes, , onNodesChange] = useNodesState(initialNodes);

  return (
    <ReactFlowProvider>
      <ReactFlow nodes={nodes} onNodesChange={onNodesChange} id={id}>
        {bgProps.map((props, idx) => (
          <Background key={idx} id={idx.toString()} {...props} />
        ))}
      </ReactFlow>
    </ReactFlowProvider>
  );
};

const Backgrounds: FC = () => (
  <div className={styles.wrapper}>
    <Flow id="flow-a" bgProps={[{ variant: BackgroundVariant.Dots }]} />
    <Flow id="flow-b" bgProps={[{ variant: BackgroundVariant.Lines, gap: [50, 50] }]} />
    <Flow id="flow-c" bgProps={[{ variant: BackgroundVariant.Cross, gap: [100, 50] }]} />
    <Flow
      id="flow-d"
      bgProps={[
        { variant: BackgroundVariant.Lines, gap: 10 },
        { variant: BackgroundVariant.Lines, gap: 100, offset: 2, color: '#ccc' },
      ]}
    />
  </div>
);

export default Backgrounds;

```

## /examples/react/src/examples/Backgrounds/style.module.css

```css path="/examples/react/src/examples/Backgrounds/style.module.css" 
.wrapper {
  display: flex;
  height: 100%;
}

.wrapper :global .react-flow {
  width: 100%;
  height: 100%;
}

.wrapper :global .react-flow {
  border-right: 1px solid #ddd;
}

```

## /examples/react/src/examples/Basic/index.tsx

```tsx path="/examples/react/src/examples/Basic/index.tsx" 
import { MouseEvent, useCallback, useState } from 'react';
import {
  ReactFlow,
  MiniMap,
  Background,
  BackgroundVariant,
  Controls,
  ReactFlowProvider,
  Node,
  Edge,
  useReactFlow,
  Panel,
  OnNodeDrag,
  FitViewOptions,
} from '@xyflow/react';

const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
const onNodeDragStart = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);

const printSelectionEvent = (name: string) => (_: MouseEvent, nodes: Node[]) => console.log(name, nodes);

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
  },
  {
    id: '3',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e1-3', source: '1', target: '3' },
];

const defaultEdgeOptions = {};
const fitViewOptions: FitViewOptions = {
  padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 },
};

const BasicFlow = () => {
  const {
    addNodes,
    setNodes,
    getNodes,
    setEdges,
    getEdges,
    deleteElements,
    updateNodeData,
    toObject,
    setViewport,
    fitView,
  } = useReactFlow();

  const updatePos = () => {
    setNodes((nodes) =>
      nodes.map((node) => {
        return {
          ...node,
          position: {
            x: Math.random() * 400,
            y: Math.random() * 400,
          },
        };
      })
    );
  };

  const logToObject = () => console.log(toObject());
  const resetTransform = () => setViewport({ x: 0, y: 0, zoom: 1 });

  const toggleClassnames = () => {
    setNodes((nodes) =>
      nodes.map((node) => {
        return {
          ...node,
          className: node.className === 'light' ? 'dark' : 'light',
        };
      })
    );
  };

  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' }]);
    fitView();
  };

  const onUpdateNode = () => {
    updateNodeData('1', { label: 'update' });
    updateNodeData('2', { label: 'update' });
  };
  const addNode = () => {
    addNodes({
      id: `${Math.random()}`,
      data: { label: 'Node' },
      position: { x: Math.random() * 300, y: Math.random() * 300 },
      className: 'light',
    });
    fitView();
  };
  const [isHidden, setIsHidden] = useState(false);

  const toggleVisibility = () => {
    setIsHidden(!isHidden);
  };
  return (
    <>
      <ReactFlow
        defaultNodes={initialNodes}
        defaultEdges={initialEdges}
        onNodesChange={console.log}
        onNodeClick={onNodeClick}
        onNodeDragStop={onNodeDragStop}
        onNodeDragStart={onNodeDragStart}
        onNodeDrag={onNodeDrag}
        onSelectionDragStart={printSelectionEvent('selection drag start')}
        onSelectionDrag={printSelectionEvent('selection drag')}
        onSelectionDragStop={printSelectionEvent('selection drag stop')}
        className="react-flow-basic-example"
        style={{ display: isHidden ? 'none' : 'block' }}
        minZoom={0.2}
        maxZoom={4}
        fitView
        fitViewOptions={fitViewOptions}
        defaultEdgeOptions={defaultEdgeOptions}
        selectNodesOnDrag={false}
        elevateEdgesOnSelect
        elevateNodesOnSelect={false}
        nodeDragThreshold={0}
      >
        <Background variant={BackgroundVariant.Dots} />
        <MiniMap />
        <Controls />

        <Panel position="top-right">
          <button onClick={resetTransform}>reset transform</button>
          <button onClick={updatePos}>change pos</button>
          <button onClick={toggleClassnames}>toggle classnames</button>
          <button onClick={logToObject}>toObject</button>

          <button onClick={deleteSelectedElements}>deleteSelectedElements</button>
          <button onClick={deleteSomeElements}>deleteSomeElements</button>
          <button onClick={onSetNodes}>setNodes</button>
          <button onClick={onUpdateNode}>updateNode</button>
          <button onClick={addNode}>addNode</button>
        </Panel>
      </ReactFlow>
      <button onClick={toggleVisibility} style={{ position: 'absolute', zIndex: 10, right: 10, top: 100 }}>
        {isHidden ? 'Show' : 'Hide'} Flow
      </button>
    </>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <BasicFlow />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/BrokenNodes/index.tsx

```tsx path="/examples/react/src/examples/BrokenNodes/index.tsx" 
import { useCallback, useState } from 'react';
import { ReactFlow, addEdge, Node, Connection, Edge, OnNodeDrag } from '@xyflow/react';

const nodesInit: Node[] = [
  {
    id: '1a',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
    ariaLabel: 'Input Node 1',
  },
  {
    id: '2a',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
    ariaLabel: 'Default Node 2',
  },
  {
    id: '3a',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4a',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

const edgesInit: Edge[] = [
  { id: 'e1-2', source: '1a', target: '2a', ariaLabel: undefined },
  { id: 'e1-3', source: '1a', target: '3a' },
];

const onNodesChange = () => {};
const onEdgesChange = () => {};
const BasicFlow = () => {
  const [nodes, setNodes] = useState(nodesInit);
  const [edges, setEdges] = useState(edgesInit);

  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  const onNodeDrag: OnNodeDrag = useCallback((e, node) => {
    if (isNaN(node.position.x) || isNaN(node.position.y)) {
      console.log('received NaN', node.position);
    }

    setNodes((nds) => {
      return nds.map((item) => {
        if (item.id === node.id) {
          return {
            ...item,
            position: {
              x: node.position.x,
              y: node.position.y,
            },
          };
        }
        return item;
      });
    });
  }, []);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      onNodeDrag={onNodeDrag}
    ></ReactFlow>
  );
};

export default BasicFlow;

```

## /examples/react/src/examples/CancelConnection/Timer.module.css

```css path="/examples/react/src/examples/CancelConnection/Timer.module.css" 
.Timer {
  position: absolute;
  bottom: 0;
  transition-duration: 0.3s;
  left: 50%;
  transform: translate(-50%, 100%);
  font-size: 1.5rem;
  z-index: 999;
  background: linear-gradient(#fff, #fff, #f5f5f5);
  padding: 0.8rem 1.5rem;
  border-radius: 5px;
  border: 1px solid #ccc;
  box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
}

.Timer.show {
  transform: translate(-50%, 0%) translateY(-15px);
}

.progress {
  display: none;
  position: absolute;
  bottom: 0;
  left: 0;
  height: 5px;
  background: linear-gradient(to right, #42df96, #16dfed);
}

.Timer.show .progress {
  display: block;
}
```

## /examples/react/src/examples/CancelConnection/Timer.tsx

```tsx path="/examples/react/src/examples/CancelConnection/Timer.tsx" 
import cc from 'classcat';
import styles from './Timer.module.css';

interface Props {
  duration: number;
  remaining: number;
  show: boolean;
}

export default function Timer({
  duration,
  remaining,
  show,
}: Props) {
  const percentage = 100 - (remaining / duration) * 100;

  return (
    <div className={cc({
      [styles.Timer]: true,
      [styles.show]: show,
    })}>
      <div className={styles.progress} style={{
        width: `${percentage}%`,
      }} />
      Connection will be canceled in {remaining} seconds
    </div>
  )
}
```

## /examples/react/src/examples/CancelConnection/data.ts

```ts path="/examples/react/src/examples/CancelConnection/data.ts" 
import { Edge, Node } from '@xyflow/react';

export const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
  },
  {
    id: '3',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

export const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e1-3', source: '1', target: '3' },
];

```

## /examples/react/src/examples/CancelConnection/hooks/useCountdown.ts

```ts path="/examples/react/src/examples/CancelConnection/hooks/useCountdown.ts" 
import { useRef, useState } from 'react';

const useCountdown = (callback: () => void) => {
  const interval = useRef<NodeJS.Timer>();
  const [remaining, setRemaining] = useState(0);

  const start = (duration: number) => {
    setRemaining(duration);

    interval.current = setInterval(() => {
      setRemaining((prev) => {
        if (prev === 1) {
          clearInterval(interval.current);
          callback();
        }

        return prev - 1;
      });
    }, 1000);
  };

  const stop = () => {
    clearInterval(interval.current);
    setRemaining(0);
  };

  return {
    start,
    remaining,
    stop,
    counting: remaining > 0,
  };
};

export default useCountdown;

```

## /examples/react/src/examples/CancelConnection/index.tsx

```tsx path="/examples/react/src/examples/CancelConnection/index.tsx" 
import {
  ReactFlow,
  Background,
  MiniMap,
  addEdge,
  ReactFlowProvider,
  Connection,
  Edge,
  useNodesState,
  useEdgesState,
  OnConnectStart,
  OnConnectEnd,
  useStore,
} from '@xyflow/react';

import useCountdown from './hooks/useCountdown';
import { initialEdges, initialNodes } from './data';
import Timer from './Timer';

const CANCEL_AFTER = 5; // seconds

const CancelConnection = () => {
  const [nodes, _, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const cancelConnection = useStore((state) => state.cancelConnection);

  // Cancels connection after 5 seconds
  const countdown = useCountdown(() => cancelConnection());
  const onConnectStart: OnConnectStart = () => countdown.start(CANCEL_AFTER);
  const onConnectEnd: OnConnectEnd = () => countdown.stop();

  const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));

  return (
    <>
      <Timer duration={CANCEL_AFTER} show={countdown.counting} remaining={countdown.remaining} />
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnectStart={onConnectStart}
        onConnectEnd={onConnectEnd}
        onConnect={onConnect}
        fitView
        maxZoom={2}
      >
        <Background />
        <MiniMap />
      </ReactFlow>
    </>
  );
};

export default () => (
  <ReactFlowProvider>
    <CancelConnection />
  </ReactFlowProvider>
);

```

## /examples/react/src/examples/ClickDistance/index.tsx

```tsx path="/examples/react/src/examples/ClickDistance/index.tsx" 
import { useCallback, useState } from 'react';
import { ReactFlow, addEdge, Node, Connection, Edge, useNodesState, useEdgesState, Panel } from '@xyflow/react';

const initNodes: Node[] = [
  {
    id: '1a',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
    ariaLabel: 'Input Node 1',
  },
  {
    id: '2a',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
    ariaLabel: 'Default Node 2',
  },
  {
    id: '3a',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4a',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

const initEdges: Edge[] = [
  { id: 'e1-2', source: '1a', target: '2a', ariaLabel: undefined },
  { id: 'e1-3', source: '1a', target: '3a' },
];

const onPaneClick = () => console.log('pane click');

const BasicFlow = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState(initNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
  const [paneClickDistance, setPaneClickDistance] = useState(0);

  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      paneClickDistance={paneClickDistance}
      onPaneClick={onPaneClick}
    >
      <Panel position="top-right">
        <input
          type="range"
          min={0}
          max={100}
          value={paneClickDistance}
          onChange={(evt) => setPaneClickDistance(+evt.target.value)}
        />
        click distance: {paneClickDistance}
      </Panel>
    </ReactFlow>
  );
};

export default BasicFlow;

```

## /examples/react/src/examples/ColorMode/index.tsx

```tsx path="/examples/react/src/examples/ColorMode/index.tsx" 
import { ChangeEventHandler, useCallback, useState } from 'react';
import {
  ReactFlow,
  addEdge,
  Node,
  useNodesState,
  useEdgesState,
  OnConnect,
  Edge,
  MiniMap,
  Background,
  Controls,
  Panel,
  ColorMode,
  Position,
} from '@xyflow/react';

import './style.css';

const nodeDefaults = {
  sourcePosition: Position.Right,
  targetPosition: Position.Left,
};

const initialNodes: Node[] = [
  { id: 'A', type: 'input', position: { x: 0, y: 150 }, data: { label: 'A' }, ...nodeDefaults },
  { id: 'B', position: { x: 250, y: 0 }, data: { label: 'B' }, ...nodeDefaults },
  { id: 'C', position: { x: 250, y: 150 }, data: { label: 'C' }, ...nodeDefaults },
  { id: 'D', position: { x: 250, y: 300 }, data: { label: 'D' }, ...nodeDefaults },
];

const initialEdges: Edge[] = [
  {
    id: 'A-B',
    source: 'A',
    target: 'B',
  },
  {
    id: 'A-C',
    source: 'A',
    target: 'C',
  },
  {
    id: 'A-D',
    source: 'A',
    target: 'D',
  },
];

const ColorModeFlow = () => {
  const [colorMode, setColorMode] = useState<ColorMode>('light');
  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect: OnConnect = useCallback(
    (params) => {
      console.log('on connect', params);
      setEdges((eds) => addEdge(params, eds));
    },
    [setEdges]
  );

  const onChange: ChangeEventHandler<HTMLSelectElement> = (evt) => setColorMode(evt.target.value as ColorMode);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      colorMode={colorMode}
      fitView
    >
      <MiniMap />
      <Background />
      <Controls />

      <Panel position="top-right">
        <select onChange={onChange} data-testid="colormode-select">
          <option value="light">light</option>
          <option value="dark">dark</option>
          <option value="system">system</option>
        </select>
      </Panel>
    </ReactFlow>
  );
};

export default ColorModeFlow;

```

## /examples/react/src/examples/ColorMode/style.css

```css path="/examples/react/src/examples/ColorMode/style.css" 

```

## /examples/react/src/examples/ControlledUncontrolled/index.tsx

```tsx path="/examples/react/src/examples/ControlledUncontrolled/index.tsx" 
import {
  ReactFlow,
  useReactFlow,
  Node,
  Edge,
  ReactFlowProvider,
  useNodesState,
  useEdgesState,
  Background,
  BackgroundVariant,
} from '@xyflow/react';

const defaultNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
  },
  {
    id: '3',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

const defaultEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2' },
  { id: 'e1-3', source: '1', target: '3' },
];

const defaultEdgeOptions = {
  animated: true,
};

// This is bad practise. You should either use a controlled or an uncontrolled component.
// This is just an example for testing the API.
const ControlledUncontrolled = () => {
  const [nodes, , onNodesChange] = useNodesState(defaultNodes);
  const [edges, , onEdgesChange] = useEdgesState(defaultEdges);
  const instance = useReactFlow();

  const logToObject = () => console.log(instance.toObject());
  const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 });

  const updateNodePositions = () => {
    instance.setNodes((nodes) =>
      nodes.map((node) => {
        return {
          ...node,
          position: {
            x: Math.random() * 400,
            y: Math.random() * 400,
          },
        };
      })
    );
  };

  const updateEdgeColors = () => {
    instance.setEdges((edges) =>
      edges.map((edge) => {
        return {
          ...edge,
          style: {
            stroke: '#ff5050',
          },
        };
      })
    );
  };

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      defaultNodes={defaultNodes}
      defaultEdges={defaultEdges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      defaultEdgeOptions={defaultEdgeOptions}
      fitView
    >
      <Background variant={BackgroundVariant.Lines} />

      <div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
        <button onClick={resetTransform} style={{ marginRight: 5 }}>
          reset transform
        </button>
        <button onClick={updateNodePositions} style={{ marginRight: 5 }}>
          change pos
        </button>
        <button onClick={updateEdgeColors} style={{ marginRight: 5 }}>
          red edges
        </button>
        <button onClick={logToObject}>toObject</button>
      </div>
    </ReactFlow>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <ControlledUncontrolled />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/ControlledViewport/index.tsx

```tsx path="/examples/react/src/examples/ControlledViewport/index.tsx" 
import { useCallback, useState } from 'react';
import {
  ReactFlow,
  addEdge,
  Node,
  Connection,
  Edge,
  useNodesState,
  useEdgesState,
  Viewport,
  Panel,
  MiniMap,
  Background,
  ReactFlowProvider,
  useReactFlow,
  Controls,
} from '@xyflow/react';

const initNodes: Node[] = [
  {
    id: '1a',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
    ariaLabel: 'Input Node 1',
  },
  {
    id: '2a',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
    ariaLabel: 'Default Node 2',
  },
  {
    id: '3a',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4a',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

const initEdges: Edge[] = [
  { id: 'e1-2', source: '1a', target: '2a', ariaLabel: undefined },
  { id: 'e1-3', source: '1a', target: '3a' },
];

const Flow = () => {
  const [nodes, _, onNodesChange] = useNodesState(initNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
  const [viewport, setViewport] = useState<Viewport>({ x: 0, y: 0, zoom: 1 });
  const [viewport2, setViewport2] = useState<Viewport>({ x: 100, y: 100, zoom: 1.5 });
  const [currentViewport, setCurrentViewport] = useState(0);
  const { fitView } = useReactFlow();

  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  const setter = currentViewport === 0 ? setViewport : setViewport2;

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      viewport={currentViewport === 0 ? viewport : viewport2}
      onViewportChange={setter}
    >
      <Panel position="top-left">
        <button onClick={() => setter((vp) => ({ ...vp, y: vp.y + 10 }))}>update viewport</button>
        <button onClick={() => fitView()}>fitView</button>
        <button onClick={() => setCurrentViewport(currentViewport === 0 ? 1 : 0)}>toggle viewport</button>
      </Panel>

      <MiniMap />
      <Background />
      <Controls />
    </ReactFlow>
  );
};

export default () => (
  <ReactFlowProvider>
    <Flow />
  </ReactFlowProvider>
);

```

## /examples/react/src/examples/CustomConnectionLine/ConnectionLine.tsx

```tsx path="/examples/react/src/examples/CustomConnectionLine/ConnectionLine.tsx" 
import { ConnectionLineComponentProps } from '@xyflow/react';

function ConnectionLine({ fromX, fromY, toX, toY, pointer }: ConnectionLineComponentProps) {
  console.log('pointer', pointer);
  return (
    <>
      <path
        fill="none"
        stroke="#222"
        strokeWidth={1.5}
        className="animated"
        d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
      />
      <circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
    </>
  );
}

export default ConnectionLine;

```

## /examples/react/src/examples/CustomConnectionLine/index.tsx

```tsx path="/examples/react/src/examples/CustomConnectionLine/index.tsx" 
import { useCallback } from 'react';
import {
  ReactFlow,
  Node,
  addEdge,
  Connection,
  Edge,
  useNodesState,
  useEdgesState,
  Background,
  BackgroundVariant,
} from '@xyflow/react';

import ConnectionLine from './ConnectionLine';

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'default',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
  },
];
const initialEdges: Edge[] = [];

const ConnectionLineFlow = () => {
  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      connectionLineComponent={ConnectionLine}
      onConnect={onConnect}
      connectionDragThreshold={25}
    >
      <Background variant={BackgroundVariant.Lines} />
    </ReactFlow>
  );
};

export default ConnectionLineFlow;

```

## /examples/react/src/examples/CustomMiniMapNode/index.tsx

```tsx path="/examples/react/src/examples/CustomMiniMapNode/index.tsx" 
import { MouseEvent, CSSProperties, useCallback, useState } from 'react';

import {
  ReactFlow,
  addEdge,
  Background,
  BackgroundVariant,
  Connection,
  Controls,
  Edge,
  MiniMap,
  MiniMapNodeProps,
  Node,
  ReactFlowInstance,
  useEdgesState,
  useNodesState,
  Panel,
} from '@xyflow/react';

const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);

const CustomMiniMapNode = ({ x, y, width, height }: MiniMapNodeProps) => {
  return <circle cx={x} cy={y} r={Math.max(width, height) / 2} fill="#ffcc00" />;
};

const CustomMiniMapNodeFlow = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
  const [hideAllNodes, setHideAllNodes] = useState(false);

  const onConnect = useCallback((params: Connection | Edge) => setEdges((els) => addEdge(params, els)), [setEdges]);
  const addRandomNode = () => {
    const nodeId = (nodes.length + 1).toString();
    const newNode: Node = {
      id: nodeId,
      data: { label: `Node: ${nodeId}` },
      position: {
        x: Math.random() * window.innerWidth,
        y: Math.random() * window.innerHeight,
      },
      hidden: hideAllNodes,
    };
    setNodes((nds) => nds.concat(newNode));
  };

  const toggleHideAllNodes = () => {
    setHideAllNodes(prev => {
      const next = !prev;
      setNodes(nds => nds.map(n => ({ ...n, hidden: next })));
      setEdges(eds => eds.map(e => ({ ...e, hidden: next })));
      return next;
    });
  };

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onInit={onInit}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onNodeClick={onNodeClick}
      onConnect={(p) => onConnect(p)}
      onNodeDragStop={onNodeDragStop}
      onlyRenderVisibleElements={true}
    >
      <Controls />
      <Background variant={BackgroundVariant.Lines} />
      <MiniMap nodeComponent={CustomMiniMapNode} />

      <Panel position="top-left">
        <button type="button" onClick={addRandomNode}>
          add node
        </button>
        <button type="button" onClick={toggleHideAllNodes}>
          {hideAllNodes ? 'show all nodes' : 'hide all nodes'}
        </button>
      </Panel>
    </ReactFlow>
  );
};

export default CustomMiniMapNodeFlow;

```

## /examples/react/src/examples/CustomNode/ColorSelectorNode.tsx

```tsx path="/examples/react/src/examples/CustomNode/ColorSelectorNode.tsx" 
import React, { memo, CSSProperties, useCallback } from 'react';
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from '@xyflow/react';

import type { ColorSelectorNode } from '.';

const targetHandleStyle: CSSProperties = { background: '#555' };
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
const sourceHandleStyleB: CSSProperties = {
  ...targetHandleStyle,
  bottom: 10,
  top: 'auto',
};

const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);

function ColorSelectorNode({ data, isConnectable }: NodeProps<ColorSelectorNode>) {
  const onStart = useCallback((viewport: Viewport) => console.log('onStart', viewport), []);
  const onChange = useCallback((viewport: Viewport) => console.log('onChange', viewport), []);
  const onEnd = useCallback((viewport: Viewport) => console.log('onEnd', viewport), []);

  useOnViewportChange({
    onStart,
    onChange,
    onEnd,
  });

  return (
    <>
      <Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
      <div>
        Custom Color Picker Node: <strong>{data.color}</strong>
      </div>
      <input className="nodrag nokey" type="color" onChange={data.onChange} defaultValue={data.color} />
      <Handle
        type="source"
        position={Position.Right}
        id="a"
        style={sourceHandleStyleA}
        isConnectable={isConnectable}
        onMouseDown={(e) => {
          console.log('You trigger mousedown event', e);
        }}
      />
      <Handle type="source" position={Position.Right} id="b" style={sourceHandleStyleB} isConnectable={isConnectable} />
    </>
  );
}

export default memo(ColorSelectorNode);

```

## /examples/react/src/examples/CustomNode/index.tsx

```tsx path="/examples/react/src/examples/CustomNode/index.tsx" 
import { useState, useEffect, MouseEvent, ChangeEvent, useCallback, useRef } from 'react';
import {
  ReactFlow,
  MiniMap,
  Controls,
  addEdge,
  Node,
  Position,
  SnapGrid,
  useEdgesState,
  Background,
  OnNodeDrag,
  OnInit,
  applyNodeChanges,
  OnNodesChange,
  OnConnect,
  OnBeforeDelete,
  BuiltInNode,
  BuiltInEdge,
  NodeTypes,
  ReactFlowProvider,
} from '@xyflow/react';

import ColorSelectorNode from './ColorSelectorNode';

export type ColorSelectorNode = Node<
  { color: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void },
  'selectorNode'
>;
export type MyNode = BuiltInNode | ColorSelectorNode;
export type MyEdge = BuiltInEdge;

const onInit: OnInit<MyNode, MyEdge> = (reactFlowInstance) => {
  console.log('flow loaded:', reactFlowInstance);
};

const onNodeDragStop: OnNodeDrag<MyNode> = (_, node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: MyNode) => console.log('click', node);

const initBgColor = '#1A192B';

const connectionLineStyle = { stroke: '#fff' };
const snapGrid: SnapGrid = [16, 16];

const nodeTypes: NodeTypes = {
  selectorNode: ColorSelectorNode,
};

const CustomNodeFlow = () => {
  const ref = useRef(null);
  const [nodes, setNodes] = useState<MyNode[]>([]);
  const onNodesChange: OnNodesChange<MyNode> = useCallback(
    (changes) =>
      setNodes((nds) => {
        const nextNodes = applyNodeChanges(changes, nds);
        return nextNodes;
      }),
    [setNodes]
  );

  const [edges, setEdges, onEdgesChange] = useEdgesState<MyEdge>([]);

  const [bgColor, setBgColor] = useState<string>(initBgColor);

  useEffect(() => {
    const onChange = (event: ChangeEvent<HTMLInputElement>) => {
      setNodes((nds) =>
        nds.map((node) => {
          if (node.id !== '2' || node.type !== 'selectorNode') {
            return node;
          }

          const color = event.target.value;

          setBgColor(color);

          return {
            ...node,
            data: {
              ...node.data,
              color,
            },
          };
        })
      );
    };

    setNodes([
      {
        id: '1',
        type: 'input',
        data: { label: 'An input node' },
        position: { x: 0, y: 50 },
        sourcePosition: Position.Right,
      },
      {
        id: '2',
        type: 'selectorNode',
        data: { onChange: onChange, color: initBgColor },
        style: { border: '1px solid #777', padding: 10 },
        position: { x: 250, y: 50 },
      },
      {
        id: '3',
        type: 'output',
        data: { label: 'Output A' },
        position: { x: 550, y: 25 },
        targetPosition: Position.Left,
      },
      {
        id: '4',
        type: 'output',
        data: { label: 'Output B' },
        position: { x: 550, y: 100 },
        targetPosition: Position.Left,
      },
    ]);

    setEdges([
      {
        id: 'e1-2',
        source: '1',
        target: '2',
        animated: true,
        style: { stroke: '#fff' },
      },
      {
        id: 'e2a-3',
        source: '2',
        sourceHandle: 'a',
        target: '3',
        animated: true,
        style: { stroke: '#fff' },
      },
      {
        id: 'e2b-4',
        source: '2',
        sourceHandle: 'b',
        target: '4',
        animated: true,
        style: { stroke: '#fff' },
      },
    ]);
  }, []);

  const onConnect: OnConnect = useCallback(
    (connection) => setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds)),
    [setEdges]
  );

  const onBeforeDelete: OnBeforeDelete<MyNode, MyEdge> = useCallback(async (params) => true, []);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onNodeClick={onNodeClick}
      onConnect={onConnect}
      onNodeDragStop={onNodeDragStop}
      onInit={onInit}
      nodeTypes={nodeTypes}
      connectionLineStyle={connectionLineStyle}
      snapToGrid={true}
      snapGrid={snapGrid}
      fitView
      minZoom={0.3}
      maxZoom={2}
      onBeforeDelete={onBeforeDelete}
      ref={ref}
    >
      <MiniMap<MyNode>
        nodeStrokeColor={(n: MyNode): string => {
          if (n.type === 'input') return '#0041d0';
          if (n.type === 'selectorNode') return bgColor;
          if (n.type === 'output') return '#ff0072';

          return '#eee';
        }}
        nodeColor={(n: MyNode): string => {
          if (n.type === 'selectorNode') return bgColor;

          return '#fff';
        }}
      />
      <Controls />
      <Background bgColor={bgColor} />
    </ReactFlow>
  );
};

export default () => (
  <ReactFlowProvider>
    <CustomNodeFlow />
  </ReactFlowProvider>
);

```

## /examples/react/src/examples/DefaultEdgeOverwrite/index.tsx

```tsx path="/examples/react/src/examples/DefaultEdgeOverwrite/index.tsx" 
import {
  ReactFlow,
  Node,
  Edge,
  ReactFlowProvider,
  Background,
  BackgroundVariant,
  EdgeProps,
  getBezierPath,
} from '@xyflow/react';

const initialNodes: Node[] = [
  {
    id: '1',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
  },
];

const initialEdges: Edge[] = [
  {
    id: 'e1-2',
    source: '1',
    target: '2',
    type: 'unregistered', // This will fallback to custom default
  },
];

const CustomEdge = ({ sourceX, sourceY, targetX, targetY }: EdgeProps) => {
  const [edgePath] = getBezierPath({
    sourceX,
    sourceY,
    targetX,
    targetY,
  });

  return (
    <>
      <path d={edgePath} stroke="red" strokeWidth={3} fill="none" strokeDasharray="5,5" />
    </>
  );
};

const edgeTypes = {
  default: CustomEdge,
};

const DefaultEdgeOverwrite = () => {
  return (
    <ReactFlow defaultNodes={initialNodes} defaultEdges={initialEdges} edgeTypes={edgeTypes} fitView>
      <Background variant={BackgroundVariant.Lines} />
    </ReactFlow>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <DefaultEdgeOverwrite />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/DefaultNodeOverwrite/index.tsx

```tsx path="/examples/react/src/examples/DefaultNodeOverwrite/index.tsx" 
import { ReactFlow, Node, ReactFlowProvider, Background, BackgroundVariant, NodeProps } from '@xyflow/react';

const initialNodes: Node[] = [
  {
    id: '1',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    type: 'unregistered',
    position: { x: 100, y: 100 },
    className: 'light',
  },
];

const CustomNode = (_: NodeProps) => {
  return <div>Custom node</div>;
};

const nodeTypes = {
  default: CustomNode,
};

const DefaultNodeOverwrite = () => {
  return (
    <ReactFlow defaultNodes={initialNodes} nodeTypes={nodeTypes} fitView>
      <Background variant={BackgroundVariant.Lines} />
    </ReactFlow>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <DefaultNodeOverwrite />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/DefaultNodes/index.tsx

```tsx path="/examples/react/src/examples/DefaultNodes/index.tsx" 
import {
  ReactFlow,
  useReactFlow,
  Node,
  Edge,
  ReactFlowProvider,
  Background,
  BackgroundVariant,
  Panel,
} from '@xyflow/react';

const defaultNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
    className: 'light',
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
    className: 'light',
  },
  {
    id: '3',
    type: 'output',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
    className: 'light',
  },
  {
    id: '4',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
    className: 'light',
  },
];

const defaultEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2' },
  { id: 'e1-3', source: '1', target: '3' },
];

const defaultEdgeOptions = {
  animated: true,
};

const DefaultNodes = () => {
  const instance = useReactFlow();

  const logToObject = () => console.log(instance.toObject());
  const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 });

  const updateNodePositions = () => {
    instance.setNodes((nodes) =>
      nodes.map((node) => ({
        ...node,
        position: {
          x: Math.random() * 400,
          y: Math.random() * 400,
        },
      }))
    );
  };

  const updateEdgeColors = () => {
    instance.setEdges((edges) =>
      edges.map((edge) => ({
        ...edge,
        style: {
          stroke: '#ff5050',
        },
      }))
    );
  };

  return (
    <ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} defaultEdgeOptions={defaultEdgeOptions} fitView>
      <Background variant={BackgroundVariant.Lines} />

      <Panel position="top-right">
        <button onClick={resetTransform}>reset transform</button>
        <button onClick={updateNodePositions}>change pos</button>
        <button onClick={updateEdgeColors}>red edges</button>
        <button onClick={logToObject}>toObject</button>
      </Panel>
    </ReactFlow>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <DefaultNodes />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/DetachedHandle/index.tsx

```tsx path="/examples/react/src/examples/DetachedHandle/index.tsx" 
import {
  ReactFlow,
  Node,
  ReactFlowProvider,
  Background,
  BackgroundVariant,
  NodeProps,
  Handle,
  Position,
} from '@xyflow/react';

import './style.css';

const initialNodes: Node[] = [
  {
    id: '1',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
  },
  {
    id: '2',
    data: { label: 'Node 2' },
    position: { x: 50, y: 100 },
  },
  {
    id: '3',
    data: { label: 'Node 3' },
    position: { x: 450, y: 100 },
  },
];

const CustomNode = (_: NodeProps) => {
  return (
    <>
      <Handle type="target" position={Position.Left} />
      <div>Custom node</div>
      <Handle type="source" position={Position.Right}>
        <button className="detached-handle">➡️</button>
      </Handle>
    </>
  );
};

const nodeTypes = {
  default: CustomNode,
};

const DetachedHandle = () => {
  return (
    <ReactFlow defaultNodes={initialNodes} defaultEdges={[]} connectionRadius={10} nodeTypes={nodeTypes} fitView>
      <Background variant={BackgroundVariant.Lines} />
    </ReactFlow>
  );
};

export default function App() {
  return (
    <ReactFlowProvider>
      <DetachedHandle />
    </ReactFlowProvider>
  );
}

```

## /examples/react/src/examples/DetachedHandle/style.css

```css path="/examples/react/src/examples/DetachedHandle/style.css" 
.detached-handle {
    position: absolute;
    top: 50%;
    left: 1rem;
    transform: translateY(-50%);
    width: 2rem;
    height: 2rem;
    border: none;
    border-radius: 50%;
}
```

## /examples/react/src/examples/DevTools/DevTools/ChangeLogger.tsx

```tsx path="/examples/react/src/examples/DevTools/DevTools/ChangeLogger.tsx" 
import { useEffect, useRef, useState } from 'react';
import { NodeChange, OnNodesChange, useStore, useStoreApi } from '@xyflow/react';

type ChangeLoggerProps = {
  color?: string;
  limit?: number;
};

type ChangeInfoProps = {
  change: NodeChange;
};

function ChangeInfo({ change }: ChangeInfoProps) {
  const id = 'id' in change ? change.id : '-';
  const { type } = change;

  return (
    <div style={{ marginBottom: 4 }}>
      <div>node id: {id}</div>
      <div>
        {type === 'add' ? JSON.stringify(change.item, null, 2) : null}
        {type === 'dimensions' ? `${change.dimensions?.width} × ${change.dimensions?.height}` : null}
        {type === 'position' ? `position: ${change.position?.x.toFixed(1)}, ${change.position?.y.toFixed(1)}` : null}
        {type === 'remove' ? 'remove' : null}
        {type === 'replace' ? JSON.stringify(change.item, null, 2) : null}
        {type === 'select' ? (change.selected ? 'select' : 'unselect') : null}
      </div>
    </div>
  );
}

export default function ChangeLogger({ limit = 20 }: ChangeLoggerProps) {
  const [changes, setChanges] = useState<NodeChange[]>([]);
  const onNodesChangeIntercepted = useRef(false);
  const onNodesChange = useStore((s) => s.onNodesChange);
  const store = useStoreApi();

  useEffect(() => {
    if (!onNodesChange || onNodesChangeIntercepted.current) {
      return;
    }

    onNodesChangeIntercepted.current = true;
    const userOnNodesChange = onNodesChange;

    const onNodesChangeLogger: OnNodesChange = (changes) => {
      userOnNodesChange(changes);

      setChanges((c) => {
        changes.forEach((change) => {
          if (c.length >= limit) {
            c.pop();
          }

          c = [change, ...c];
        });
        return c;
      });
    };

    store.setState({ onNodesChange: onNodesChangeLogger });
  }, [onNodesChange]);

  return (
    <div className="react-flow__devtools-changelogger">
      <div className="react-flow__devtools-title">Change Logger</div>
      {changes.length === 0 ? (
        <>no changes triggered</>
      ) : (
        changes.map((change, index) => <ChangeInfo key={index} change={change} />)
      )}
    </div>
  );
}

```

## /examples/react/src/examples/DevTools/DevTools/NodeInspector.tsx

```tsx path="/examples/react/src/examples/DevTools/DevTools/NodeInspector.tsx" 
import { useNodes, ViewportPortal } from '@xyflow/react';

type NodeInfoProps = {
  id: string;
  type: string;
  x: number;
  y: number;
  width?: number;
  height?: number;
  data: any;
};

function NodeInfo({ id, type, x, y, width, height, data }: NodeInfoProps) {
  if (!width || !height) {
    return null;
  }

  return (
    <div
      className="react-flow__devtools-nodeinfo"
      style={{
        position: 'absolute',
        transform: `translate(${x}px, ${y + height}px)`,
        width: width * 2,
      }}
    >
      <div>id: {id}</div>
      <div>type: {type}</div>
      <div>
        position: {x.toFixed(1)}, {y.toFixed(1)}
      </div>
      <div>
        dimensions: {width} × {height}
      </div>
      <div>data: {JSON.stringify(data, null, 2)}</div>
    </div>
  );
}

export default function NodeInspector() {
  const nodes = useNodes();

  return (
    <ViewportPortal>
      <div className="react-flow__devtools-nodeinspector">
        {nodes.map((node) => {
          const x = node?.position?.x || 0;
          const y = node?.position?.y || 0;
          const width = node.measured?.width || 0;
          const height = node.measured?.height || 0;

          return (
            <NodeInfo
              key={node.id}
              id={node.id}
              type={node.type || 'default'}
              x={x}
              y={y}
              width={width}
              height={height}
              data={node.data}
            />
          );
        })}
      </div>
    </ViewportPortal>
  );
}

```

## /examples/react/src/examples/DevTools/DevTools/index.tsx

```tsx path="/examples/react/src/examples/DevTools/DevTools/index.tsx" 
import { useState, type Dispatch, type SetStateAction, type ReactNode, HTMLAttributes } from 'react';
import { Panel, PanelPosition } from '@xyflow/react';

import NodeInspector from './NodeInspector';
import ChangeLogger from './ChangeLogger';

import './style.css';

export default function ReactFlowDevTools({ position = 'top-left' }: { position?: PanelPosition }) {
  const [nodeInspectorActive, setNodeInspectorActive] = useState(false);
  const [changeLoggerActive, setChangeLoggerActive] = useState(false);

  return (
    <div className="react-flow__devtools">
      <Panel position={position}>
        <DevToolButton setActive={setNodeInspectorActive} active={nodeInspectorActive} title="Toggle Node Inspector">
          Node Inspector
        </DevToolButton>
        <DevToolButton setActive={setChangeLoggerActive} active={changeLoggerActive} title="Toggle Change Logger">
          Change Logger
        </DevToolButton>
      </Panel>
      {changeLoggerActive && <ChangeLogger />}
      {nodeInspectorActive && <NodeInspector />}
    </div>
  );
}

function DevToolButton({
  active,
  setActive,
  children,
  ...rest
}: {
  active: boolean;
  setActive: Dispatch<SetStateAction<boolean>>;
  children: ReactNode;
} & HTMLAttributes<HTMLButtonElement>) {
  return (
    <button onClick={() => setActive((a) => !a)} className={active ? 'active' : ''} {...rest}>
      {children}
    </button>
  );
}

```

## /examples/react/src/examples/DevTools/DevTools/style.css

```css path="/examples/react/src/examples/DevTools/DevTools/style.css" 
.react-flow__devtools {
  --border-radius: 4px;
  --highlight-color: rgba(238, 58, 115, 1);
  --font: monospace, sans-serif;

  border-radius: var(--border-radius);
  font-size: 11px;
  font-family: var(--font);
}

.react-flow__devtools button {
  background: white;
  border: none;
  padding: 5px 15px;
  color: #222;
  font-weight: bold;
  font-size: 12px;
  cursor: pointer;
  font-family: var(--font);
  background-color: #f4f4f4;
}

.react-flow__devtools button:hover {
  background: var(--highlight-color);
  color: white;
}

.react-flow__devtools button.active {
  background: var(--highlight-color);
  color: white;
}

.react-flow__devtools button:first-child {
  border-radius: var(--border-radius) 0 0 var(--border-radius);
  border-right: 1px solid #ddd;
}

.react-flow__devtools button:last-child {
  border-radius: 0 var(--border-radius) var(--border-radius) 0;
}

.react-flow__devtools-changelogger {
  pointer-events: none;
  position: relative;
  top: 50px;
  left: 20px;
  font-family: var(--font);
}

.react-flow__devtools-title {
  font-weight: bold;
  margin-bottom: 5px;
}

.react-flow__devtools-nodeinspector {
  pointer-events: none;
  font-family: monospace, sans-serif;
  font-size: 10px;
}

.react-flow__devtools-nodeinfo {
  top: 5px;
}

```

## /examples/react/src/examples/DevTools/index.tsx

```tsx path="/examples/react/src/examples/DevTools/index.tsx" 
import { useCallback } from 'react';
import { ReactFlow, addEdge, Node, Connection, Edge, useNodesState, useEdgesState } from '@xyflow/react';

import DevTools from './DevTools';

const initNodes: Node[] = [
  {
    id: '1a',
    type: 'input',
    data: { label: 'Node 1' },
    position: { x: 250, y: 5 },
  },
  {
    id: '2a',
    data: { label: 'Node 2' },
    position: { x: 100, y: 100 },
  },
  {
    id: '3a',
    data: { label: 'Node 3' },
    position: { x: 400, y: 100 },
  },
  {
    id: '4a',
    data: { label: 'Node 4' },
    position: { x: 400, y: 200 },
  },
];

const initEdges: Edge[] = [
  { id: 'e1-2', source: '1a', target: '2a' },
  { id: 'e1-3', source: '1a', target: '3a' },
];

const BasicFlow = () => {
  const [nodes, , onNodesChange] = useNodesState(initNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);

  const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      fitView
    >
      <DevTools />
    </ReactFlow>
  );
};

export default BasicFlow;

```

## /examples/react/src/examples/DragHandle/DragHandleNode.tsx

```tsx path="/examples/react/src/examples/DragHandle/DragHandleNode.tsx" 
import { memo, FC } from 'react';
import { Handle, Position, NodeProps, Connection, Edge } from '@xyflow/react';

const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);

const labelStyle = {
  display: 'flex',
  alignItems: 'center',
};

const dragHandleStyle = {
  display: 'inline-block',
  width: 25,
  height: 25,
  backgroundColor: 'teal',
  marginLeft: 5,
  borderRadius: '50%',
};

const ColorSelectorNode: FC<NodeProps> = () => {
  return (
    <>
      <Handle type="target" position={Position.Left} onConnect={onConnect} />
      <div style={labelStyle}>
        Only draggable here → <span className="custom-drag-handle" style={dragHandleStyle} />
      </div>
      <Handle type="source" position={Position.Right} />
    </>
  );
};

export default memo(ColorSelectorNode);

```

## /examples/react/src/examples/DragHandle/index.tsx

```tsx path="/examples/react/src/examples/DragHandle/index.tsx" 
import { MouseEvent } from 'react';
import { ReactFlow, Node, Edge, useNodesState, useEdgesState } from '@xyflow/react';

import DragHandleNode from './DragHandleNode';

const nodeTypes = {
  dragHandleNode: DragHandleNode,
};

const initialNodes: Node[] = [
  {
    id: '2',
    type: 'dragHandleNode',
    dragHandle: '.custom-drag-handle',
    style: { border: '1px solid #ddd', padding: '20px 40px' },
    position: { x: 200, y: 200 },
    data: {},
  },
];

const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);

const initialEdges: Edge[] = [];

const DragHandleFlow = () => {
  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges] = useEdgesState(initialEdges);

  return (
    <ReactFlow
      nodes={nodes}
      onNodesChange={onNodesChange}
      edges={edges}
      nodeTypes={nodeTypes}
      onNodeClick={onNodeClick}
      nodeDragThreshold={0}
    />
  );
};

export default DragHandleFlow;

```

## /examples/react/src/examples/DragNDrop/Sidebar.tsx

```tsx path="/examples/react/src/examples/DragNDrop/Sidebar.tsx" 
import { DragEvent } from 'react';

import styles from './dnd.module.css';

const onDragStart = (event: DragEvent, nodeType: string) => {
  event.dataTransfer.setData('application/reactflow', nodeType);
  event.dataTransfer.effectAllowed = 'move';
};

const Sidebar = () => {
  return (
    <aside className={styles.aside}>
      <div className={styles.description}>You can drag these nodes to the pane on the left.</div>
      <div className="react-flow__node-input" onDragStart={(event: DragEvent) => onDragStart(event, 'input')} draggable>
        Input Node
      </div>
      <div
        className="react-flow__node-default"
        onDragStart={(event: DragEvent) => onDragStart(event, 'default')}
        draggable
      >
        Default Node
      </div>
      <div
        className="react-flow__node-output"
        onDragStart={(event: DragEvent) => onDragStart(event, 'output')}
        draggable
      >
        Output Node
      </div>
    </aside>
  );
};

export default Sidebar;

```

## /examples/react/src/examples/DragNDrop/dnd.module.css

```css path="/examples/react/src/examples/DragNDrop/dnd.module.css" 
.dndflow {
  flex-direction: column;
  display: flex;
  height: 100%;
}

.aside {
  border-right: 1px solid #eee;
  padding: 15px 10px;
  font-size: 12px;
  background: #fcfcfc;
}

.aside > * {
  margin-bottom: 10px;
  cursor: grab;
}

.description {
  margin-bottom: 10px;
}

.wrapper {
  flex-grow: 1;
  height: 100%;
}

@media screen and (min-width: 768px) {
  .aside {
    width: 20%;
    max-width: 180px;
  }

  .dndflow {
    flex-direction: row;
  }
}

```

## /examples/react/src/examples/DragNDrop/index.tsx

```tsx path="/examples/react/src/examples/DragNDrop/index.tsx" 
import { useState, DragEvent } from 'react';
import {
  ReactFlow,
  ReactFlowProvider,
  addEdge,
  ReactFlowInstance,
  Connection,
  Edge,
  Node,
  useNodesState,
  useEdgesState,
  Controls,
  NodeOrigin,
} from '@xyflow/react';

import Sidebar from './Sidebar';

import styles from './dnd.module.css';

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'input node' },
    position: { x: 250, y: 5 },
  },
];

const onDragOver = (event: DragEvent) => {
  event.preventDefault();
  event.dataTransfer.dropEffect = 'move';
};

let id = 0;
const getId = () => `dndnode_${id++}`;

const nodeOrigin: NodeOrigin = [0.5, 0.5];

const DnDFlow = () => {
  const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance>();
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);

  const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
  const onInit = (rfi: ReactFlowInstance) => setReactFlowInstance(rfi);

  const onDrop = (event: DragEvent) => {
    event.preventDefault();

    if (reactFlowInstance) {
      const type = event.dataTransfer.getData('application/reactflow');
      const position = reactFlowInstance.screenToFlowPosition({
        x: event.clientX,
        y: event.clientY,
      });
      const newNode: Node = {
        id: getId(),
        type,
        position,
        data: { label: `${type} node` },
      };

      setNodes((nds) => nds.concat(newNode));
    }
  };

  return (
    <div className={styles.dndflow}>
      <ReactFlowProvider initialNodes={initialNodes} initialEdges={[]}>
        <div className={styles.wrapper}>
          <ReactFlow
            nodes={nodes}
            edges={edges}
            onEdgesChange={onEdgesChange}
            onNodesChange={onNodesChange}
            onConnect={onConnect}
            onInit={onInit}
            onDrop={onDrop}
            onDragOver={onDragOver}
            nodeOrigin={nodeOrigin}
          >
            <Controls />
          </ReactFlow>
        </div>
        <Sidebar />
      </ReactFlowProvider>
    </div>
  );
};

export default DnDFlow;

```

## /examples/react/src/examples/EasyConnect/CustomConnectionLine.tsx

```tsx path="/examples/react/src/examples/EasyConnect/CustomConnectionLine.tsx" 
import { ConnectionLineComponentProps, getStraightPath } from '@xyflow/react';

function CustomConnectionLine({ fromX, fromY, toX, toY, connectionLineStyle }: ConnectionLineComponentProps) {
  const [edgePath] = getStraightPath({
    sourceX: fromX,
    sourceY: fromY,
    targetX: toX,
    targetY: toY,
  });

  return (
    <g>
      <path style={connectionLineStyle} fill="none" d={edgePath} />
      <circle cx={toX} cy={toY} fill="black" r={3} stroke="black" strokeWidth={1.5} />
    </g>
  );
}

export default CustomConnectionLine;

```

## /examples/react/src/examples/EasyConnect/CustomNode.tsx

```tsx path="/examples/react/src/examples/EasyConnect/CustomNode.tsx" 
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 (
    <div className="customNode">
      <div
        className="customNodeBody"
        style={{
          borderStyle: isTarget ? 'dashed' : 'solid',
          backgroundColor: isTarget ? '#ffcce3' : '#ccd9f6',
        }}
      >
        {/* 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 !isConnecting is true at the beginning and all handles are rendered initially. */}
        {!connection.inProgress && <Handle className="customHandle" position={Position.Right} type="source" />}
        {/* We want to disable the target handle, if the connection was started from this node */}
        {(!connection.inProgress || isTarget) && (
          <Handle className="customHandle" position={Position.Left} type="target" isConnectableStart={false} />
        )}
        {label}
      </div>
    </div>
  );
}

```

## /examples/react/src/examples/EasyConnect/FloatingEdge.tsx

```tsx path="/examples/react/src/examples/EasyConnect/FloatingEdge.tsx" 
import { EdgeProps, getStraightPath, useInternalNode } from '@xyflow/react';

import { getEdgeParams } from './utils.js';

function FloatingEdge({ id, source, target, markerEnd, style }: EdgeProps) {
  const sourceNode = useInternalNode(source);
  const targetNode = useInternalNode(target);

  if (!sourceNode || !targetNode) {
    return null;
  }

  const { sx, sy, tx, ty } = getEdgeParams(sourceNode, targetNode);

  const [edgePath] = getStraightPath({
    sourceX: sx,
    sourceY: sy,
    targetX: tx,
    targetY: ty,
  });

  return <path id={id} className="react-flow__edge-path" d={edgePath} markerEnd={markerEnd} style={style} />;
}

export default FloatingEdge;

```

## /examples/react/src/examples/EasyConnect/index.tsx

```tsx path="/examples/react/src/examples/EasyConnect/index.tsx" 
import { useCallback } from 'react';
import {
  ReactFlow,
  Node,
  Edge,
  addEdge,
  useNodesState,
  useEdgesState,
  MarkerType,
  OnConnect,
  ConnectionMode,
} from '@xyflow/react';

import CustomNode from './CustomNode';
import FloatingEdge from './FloatingEdge';
import CustomConnectionLine from './CustomConnectionLine';

import './style.css';

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'custom',
    position: { x: 0, y: 0 },
    data: {},
  },
  {
    id: '2',
    type: 'custom',
    position: { x: 250, y: 320 },
    data: {},
  },
  {
    id: '3',
    type: 'custom',
    position: { x: 40, y: 300 },
    data: {},
  },
  {
    id: '4',
    type: 'custom',
    position: { x: 300, y: 0 },
    data: {},
  },
];

const initialEdges: Edge[] = [];

const connectionLineStyle = {
  strokeWidth: 3,
  stroke: 'black',
};

const nodeTypes = {
  custom: CustomNode,
};

const edgeTypes = {
  floating: FloatingEdge,
};

const defaultEdgeOptions = {
  style: { strokeWidth: 3, stroke: 'black' },
  type: 'floating',
  markerEnd: {
    type: MarkerType.ArrowClosed,
    color: 'black',
  },
};

const EasyConnectExample = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect: OnConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      fitView
      nodeTypes={nodeTypes}
      edgeTypes={edgeTypes}
      defaultEdgeOptions={defaultEdgeOptions}
      connectionLineComponent={CustomConnectionLine}
      connectionLineStyle={connectionLineStyle}
    />
  );
};

export default EasyConnectExample;

```

## /examples/react/src/examples/EasyConnect/style.css

```css path="/examples/react/src/examples/EasyConnect/style.css" 
.customNodeBody {
  width: 150px;
  height: 80px;
  border: 3px solid black;
  position: relative;
  overflow: hidden;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: bold;
}

.customNode:before {
  content: '';
  position: absolute;
  top: -10px;
  left: 50%;
  height: 20px;
  width: 40px;
  transform: translate(-50%, 0);
  background: #d6d5e6;
  z-index: 1000;
  line-height: 1;
  border-radius: 4px;
  color: #fff;
  font-size: 9px;
  border: 2px solid #222138;
}

div.customHandle {
  width: 100%;
  height: 100%;
  background: blue;
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 0;
  transform: none;
  border: none;
  opacity: 0;
}

```

## /examples/react/src/examples/EasyConnect/utils.tsx

```tsx path="/examples/react/src/examples/EasyConnect/utils.tsx" 
import { Node, Position, MarkerType, XYPosition, InternalNode } from '@xyflow/react';

// this helper function returns the intersection point
// of the line between the center of the intersectionNode and the target node
function getNodeIntersection(intersectionNode: InternalNode, targetNode: InternalNode) {
  // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a

  const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.measured;
  const intersectionNodePosition = intersectionNode.internals.positionAbsolute;
  const targetPosition = targetNode.internals.positionAbsolute!;

  const w = intersectionNodeWidth! / 2;
  const h = intersectionNodeHeight! / 2;

  const x2 = intersectionNodePosition.x + w;
  const y2 = intersectionNodePosition.y + h;
  const x1 = targetPosition.x + w;
  const y1 = targetPosition.y + h;

  const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
  const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
  const a = 1 / (Math.abs(xx1) + Math.abs(yy1) || 1);
  const xx3 = a * xx1;
  const yy3 = a * yy1;
  const x = w * (xx3 + yy3) + x2;
  const y = h * (-xx3 + yy3) + y2;
  return { x, y };
}

// returns the position (top,right,bottom or right) passed node compared to the intersection point
function getEdgePosition(node: InternalNode, intersectionPoint: XYPosition) {
  const n = { ...node.internals.positionAbsolute, ...node };
  const nx = Math.round(n.x!);
  const ny = Math.round(n.y!);
  const px = Math.round(intersectionPoint.x);
  const py = Math.round(intersectionPoint.y);

  if (px <= nx + 1) {
    return Position.Left;
  }
  if (px >= nx + n.measured?.width! - 1) {
    return Position.Right;
  }
  if (py <= ny + 1) {
    return Position.Top;
  }
  if (py >= n.y! + n.measured?.height! - 1) {
    return Position.Bottom;
  }

  return Position.Top;
}

// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
export function getEdgeParams(source: InternalNode, target: InternalNode) {
  const sourceIntersectionPoint = getNodeIntersection(source, target);
  const targetIntersectionPoint = getNodeIntersection(target, source);

  const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
  const targetPos = getEdgePosition(target, targetIntersectionPoint);

  return {
    sx: sourceIntersectionPoint.x,
    sy: sourceIntersectionPoint.y,
    tx: targetIntersectionPoint.x,
    ty: targetIntersectionPoint.y,
    sourcePos,
    targetPos,
  };
}

export function createNodesAndEdges() {
  const nodes = [];
  const edges = [];
  const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };

  nodes.push({ id: 'target', data: { label: 'Target' }, position: center });

  for (let i = 0; i < 8; i++) {
    const degrees = i * (360 / 8);
    const radians = degrees * (Math.PI / 180);
    const x = 250 * Math.cos(radians) + center.x;
    const y = 250 * Math.sin(radians) + center.y;

    nodes.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } });

    edges.push({
      id: `edge-${i}`,
      target: 'target',
      source: `${i}`,
      type: 'floating',
      markerEnd: {
        type: MarkerType.Arrow,
      },
    });
  }

  return { nodes, edges };
}

```

## /examples/react/src/examples/EdgeRenderer/CustomEdge.tsx

```tsx path="/examples/react/src/examples/EdgeRenderer/CustomEdge.tsx" 
import { FC, MouseEvent } from 'react';
import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore } from '@xyflow/react';

const CustomEdge: FC<EdgeProps> = ({
  id,
  source,
  target,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  data,
}) => {
  const isConnectedNodeDragging = useStore((s) =>
    s.nodes.find((n) => n.dragging && (target === n.id || source === n.id))
  );

  const [edgePath, labelX, labelY] = getBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
  });

  const onClick = (event: MouseEvent) => {
    event.preventDefault();
    event.stopPropagation();

    console.log('click', data.text);
  };

  return (
    <>
      <path id={id} className="react-flow__edge-path" d={edgePath} />

      <EdgeLabelRenderer>
        <div
          style={{
            position: 'absolute',
            transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
            background: '#ffcc00',
            padding: 10,
            zIndex: isConnectedNodeDragging ? 10 : 0,
            pointerEvents: 'all',
          }}
          className="nodrag nopan"
        >
          {data.text}
          <input style={{ display: 'block' }} />
          <button onClick={onClick}>send</button>
        </div>
      </EdgeLabelRenderer>
    </>
  );
};

export default CustomEdge;

```

## /examples/react/src/examples/EdgeRenderer/CustomEdge2.tsx

```tsx path="/examples/react/src/examples/EdgeRenderer/CustomEdge2.tsx" 
import { FC } from 'react';
import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore } from '@xyflow/react';

const CustomEdge: FC<EdgeProps> = ({
  id,
  source,
  target,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  data,
}) => {
  const isConnectedNodeDragging = useStore((s) =>
    s.nodes.find((n) => n.dragging && (target === n.id || source === n.id))
  );

  const [edgePath, labelX, labelY] = getBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
  });

  return (
    <>
      <path id={id} className="react-flow__edge-path" d={edgePath} />

      <EdgeLabelRenderer>
        <div
          style={{
            position: 'absolute',
            transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
            background: 'white',
            border: '1px solid #555',
            padding: 5,
            zIndex: isConnectedNodeDragging ? 10 : 0,
          }}
        >
          {data.text}
        </div>
      </EdgeLabelRenderer>
    </>
  );
};

export default CustomEdge;

```

## /examples/react/src/examples/EdgeToolbar/CustomEdge.tsx

```tsx path="/examples/react/src/examples/EdgeToolbar/CustomEdge.tsx" 
import { getBezierPath, BaseEdge, EdgeProps, useReactFlow, getStraightPath, getSmoothStepPath } from '@xyflow/react';
import { EdgeToolbar } from '@xyflow/react';

const getPath = (props: EdgeProps) => {
  switch (props.data!.type) {
    case 'smoothstep':
      return getSmoothStepPath(props);
    case 'straight':
      return getStraightPath(props);
    default:
      return getBezierPath(props);
  }
};

export function CustomEdge(props: EdgeProps) {
  const [edgePath, centerX, centerY] = getPath(props);
  const { setEdges } = useReactFlow();

  const deleteEdge = () => {
    setEdges((edges) => edges.filter((edge) => edge.id !== props.id));
  };

  return (
    <>
      <BaseEdge id={props.id} path={edgePath} />
      <EdgeToolbar
        edgeId={props.id}
        x={centerX + 0}
        y={centerY + 0}
        alignX={props.data?.align?.[0] ?? 'center'}
        alignY={props.data?.align?.[1] ?? 'center'}
        isVisible
      >
        <button style={{}} onClick={deleteEdge}>
          Delete
        </button>
      </EdgeToolbar>
    </>
  );
}

```

## /examples/react/src/examples/EdgeToolbar/index.tsx

```tsx path="/examples/react/src/examples/EdgeToolbar/index.tsx" 
import {
  Background,
  BackgroundVariant,
  Controls,
  Edge,
  EdgeTypes,
  MiniMap,
  Node,
  Position,
  ReactFlow,
} from '@xyflow/react';

import { CustomEdge } from './CustomEdge';

const edgeTypes: EdgeTypes = {
  custom: CustomEdge,
};

const initialNodes: Node[] = [
  {
    id: '1',
    data: { label: 'Node 1', toolbarPosition: Position.Top },
    position: { x: 0, y: 0 },
    className: 'react-flow__node-default',
  },
  {
    id: '2',
    data: { label: 'Node 2', toolbarPosition: Position.Top },
    position: { x: 100, y: 150 },
    className: 'react-flow__node-default',
  },
  {
    id: '3',
    data: { label: 'Node 3', toolbarPosition: Position.Top },
    position: { x: 200, y: 0 },
    className: 'react-flow__node-default',
  },
];

const initialEdges: Edge[] = [
  {
    id: 'e1-2',
    source: '1',
    target: '2',
    type: 'custom',
    data: { type: 'smoothstep', align: ['left', 'bottom'] },
  },
  {
    id: 'e3-2',
    source: '3',
    target: '2',
    type: 'custom',
    data: { type: 'bezier', align: ['right', 'bottom'] },
  },
  {
    id: 'e1-3',
    source: '1',
    target: '3',
    type: 'custom',
    data: { type: 'straight', align: ['center', 'center'] },
  },
];

export default function EdgeToolbarExample() {
  return (
    <ReactFlow
      defaultNodes={initialNodes}
      defaultEdges={initialEdges}
      className="react-flow-edge-toolbar-example"
      minZoom={0.2}
      maxZoom={4}
      fitView
      edgeTypes={edgeTypes}
    >
      <Background variant={BackgroundVariant.Dots} />
      <MiniMap />
      <Controls />
    </ReactFlow>
  );
}

```

## /examples/react/src/examples/Edges/CustomEdge3.css

```css path="/examples/react/src/examples/Edges/CustomEdge3.css" 
@keyframes react-flow-edge-dash {
  from {
    stroke-dashoffset: 100;
  }
  to {
    stroke-dashoffset: 0;
  }
}

.react-flow__edge-custom3 {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: react-flow-edge-dash 1s linear forwards;
}

```

## /examples/react/src/examples/FloatingEdges/style.module.css

```css path="/examples/react/src/examples/FloatingEdges/style.module.css" 
.floatingedges {
  flex-direction: column;
  display: flex;
  height: 100%;
}

.floatingedges :global .react-flow__handle {
  opacity: 0;
}

```


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!