diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index e5b6d8d6..00000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# 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) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 5980f269..00000000 --- a/.changeset/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@2.1.1/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "wbkd/react-flow" }], - "commit": false, - "fixed": [], - "linked": [], - "access": "public", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] -} diff --git a/examples/react/cypress.config.ts b/examples/react/cypress.config.ts index b46eaa76..027bd690 100644 --- a/examples/react/cypress.config.ts +++ b/examples/react/cypress.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'cypress'; export default defineConfig({ e2e: { - baseUrl: 'http://localhost:3000', + baseUrl: 'http://localhost:3000/examples', viewportWidth: 1280, viewportHeight: 720, video: false, diff --git a/examples/react/index.html b/examples/react/index.html index f1cba741..828516b9 100644 --- a/examples/react/index.html +++ b/examples/react/index.html @@ -2,7 +2,7 @@ - + React Flow Examples diff --git a/examples/react/package.json b/examples/react/package.json index 73d91382..36dd09a6 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -1,5 +1,5 @@ { - "name": "@xyflow/react-examples", + "name": "react-examples", "private": true, "version": "0.0.0", "type": "module", @@ -15,25 +15,25 @@ }, "dependencies": { "@xyflow/react": "workspace:*", - "classcat": "^5.0.3", + "classcat": "^5.0.4", "dagre": "^0.8.5", "localforage": "^1.10.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.3.0", - "zustand": "^4.4.1" + "react-router-dom": "^6.18.0", + "zustand": "^4.4.6" }, "devDependencies": { "@cypress/skip-test": "^2.6.1", - "@types/dagre": "^0.7.48", - "@types/react": "^18.0.17", - "@types/react-dom": "^18.0.6", - "@vitejs/plugin-react": "4.0.4", - "@vitejs/plugin-react-swc": "^3.3.2", + "@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": "12.17.3", - "cypress-real-events": "1.10.0", - "start-server-and-test": "^1.14.0", - "typescript": "5.1.3", - "vite": "4.4.9" + "cypress-real-events": "1.11.0", + "start-server-and-test": "^2.0.2", + "typescript": "5.2.2", + "vite": "4.5.0" } } diff --git a/examples/react/public/favicon.ico b/examples/react/public/favicon.ico new file mode 100644 index 00000000..9ef0fbfd Binary files /dev/null and b/examples/react/public/favicon.ico differ diff --git a/examples/react/public/vite.svg b/examples/react/public/vite.svg deleted file mode 100644 index e7b8dfb1..00000000 --- a/examples/react/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/react/src/App/header.tsx b/examples/react/src/App/header.tsx new file mode 100644 index 00000000..37e57910 --- /dev/null +++ b/examples/react/src/App/header.tsx @@ -0,0 +1,34 @@ +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 [currentPath, setCurrentPath] = useState(location.pathname); + + useEffect(() => { + const name = routes.find((route) => route.path === currentPath)?.name; + document.title = `React Flow Examples${name ? ' - ' + name : ''}`; + navigate(currentPath); + }, [currentPath]); + + return ( + <> +
+ + React Flow Dev + + +
+ + + ); +} diff --git a/examples/react/src/App/index.tsx b/examples/react/src/App/index.tsx index b6bc5be7..98b70cf8 100644 --- a/examples/react/src/App/index.tsx +++ b/examples/react/src/App/index.tsx @@ -1,326 +1,21 @@ -import { useEffect, useState } from 'react'; -import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; +import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; +import routes from './routes'; +import GenericTestRoutes from '../generic-tests'; import Basic from '../examples/Basic'; -import Backgrounds from '../examples/Backgrounds'; -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 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 NestedNodes from '../examples/NestedNodes'; -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 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 UpdatableEdge from '../examples/UpdatableEdge'; -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 useNodesInitialized from '../examples/UseNodesInit'; - -interface IRoute { - name: string; - path: string; - component: React.ComponentType; -} - -const routes: IRoute[] = [ - { - name: 'Basic', - path: '/', - component: Basic, - }, - { - name: 'Backgrounds', - path: '/backgrounds', - component: Backgrounds, - }, - { - name: 'Cancel Connection', - path: '/cancel-connection', - component: CancelConnection, - }, - { - 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 Nodes', - path: '/default-nodes', - component: DefaultNodes, - }, - { - 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: '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 Flows', - path: '/multiflows', - component: MultiFlows, - }, - { - name: 'Nested Nodes', - path: '/nested-nodes', - component: NestedNodes, - }, - { - 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: '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: 'Updatable Edge', - path: '/updatable-edge', - component: UpdatableEdge, - }, - { - name: 'Update Node', - path: '/update-node', - component: UpdateNode, - }, - { - name: 'useNodesInitialized', - path: '/use-nodes-initialized', - component: useNodesInitialized, - }, - { - name: 'useOnSelectionChange', - path: '/use-on-selection-change', - component: UseOnSelectionChange, - }, - { - name: 'useReactFlow', - path: '/usereactflow', - component: UseReactFlow, - }, - { - name: 'useUpdateNodeInternals', - path: '/useupdatenodeinternals', - component: UseUpdateNodeInternals, - }, - { - name: 'Validation', - path: '/validation', - component: Validation, - }, - { - name: 'useKeyPress', - path: '/use-key-press', - component: UseKeyPress, - }, -]; - -const Header = () => { - const location = useLocation(); - const navigate = useNavigate(); - const [currentPath, setCurrentPath] = useState(location.pathname); - - useEffect(() => { - const name = routes.find((route) => route.path === currentPath)?.name; - document.title = `React Flow Examples${name ? ' - ' + name : ''}`; - navigate(currentPath); - }, [currentPath]); - - return ( -
- - React Flow Dev - - -
- ); -}; +import Header from './header'; export default () => ( -
- {routes.map((route) => ( - } /> - ))} + } /> + }> + } /> + {routes.map((route) => ( + } /> + ))} + + } /> ); diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts new file mode 100644 index 00000000..6c6b596c --- /dev/null +++ b/examples/react/src/App/routes.ts @@ -0,0 +1,287 @@ +import Basic from '../examples/Basic'; +import Backgrounds from '../examples/Backgrounds'; +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 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 NestedNodes from '../examples/NestedNodes'; +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 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 UpdatableEdge from '../examples/UpdatableEdge'; +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 useNodesInitialized from '../examples/UseNodesInit'; + +export interface IRoute { + name: string; + path: string; + component: React.ComponentType; +} + +const routes: IRoute[] = [ + { + name: 'Basic', + path: 'basic', + component: Basic, + }, + { + name: 'Backgrounds', + path: 'backgrounds', + component: Backgrounds, + }, + { + name: 'Cancel Connection', + path: 'cancel-connection', + component: CancelConnection, + }, + { + 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 Nodes', + path: 'default-nodes', + component: DefaultNodes, + }, + { + 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: '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 Flows', + path: 'multiflows', + component: MultiFlows, + }, + { + name: 'Nested Nodes', + path: 'nested-nodes', + component: NestedNodes, + }, + { + 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: '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: 'Updatable Edge', + path: 'updatable-edge', + component: UpdatableEdge, + }, + { + name: 'Update Node', + path: 'update-node', + component: UpdateNode, + }, + { + name: 'useNodesInitialized', + path: 'use-nodes-initialized', + component: useNodesInitialized, + }, + { + name: 'useOnSelectionChange', + path: 'use-on-selection-change', + component: UseOnSelectionChange, + }, + { + name: 'useReactFlow', + path: 'usereactflow', + component: UseReactFlow, + }, + { + name: 'useUpdateNodeInternals', + path: 'useupdatenodeinternals', + component: UseUpdateNodeInternals, + }, + { + name: 'Validation', + path: 'validation', + component: Validation, + }, + { + name: 'useKeyPress', + path: 'use-key-press', + component: UseKeyPress, + }, +]; + +export default routes; diff --git a/examples/react/src/app.d.ts b/examples/react/src/app.d.ts new file mode 100644 index 00000000..3c245231 --- /dev/null +++ b/examples/react/src/app.d.ts @@ -0,0 +1,11 @@ +import { BackgroundProps, ControlProps, Edge, MiniMapProps, Node, PanelProps, ReactFlowProps } from '@xyflow/react'; + +declare global { + interface FlowConfig { + flowProps: Omit & { nodes: Node[]; edges: Edge[] }; + panelProps: PanelProps; + backgroundProps: BackgroundProps; + controlsProps: ControlProps; + minimapProps: MiniMapProps; + } +} diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 21f48ec5..4f1a51d2 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -41,6 +41,7 @@ const initialNodes: Node[] = [ data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light', + connectable: false, }, ]; diff --git a/examples/react/src/examples/Undirectional/index.tsx b/examples/react/src/examples/Undirectional/index.tsx index d9391541..c4cf14a5 100644 --- a/examples/react/src/examples/Undirectional/index.tsx +++ b/examples/react/src/examples/Undirectional/index.tsx @@ -183,7 +183,7 @@ const getId = () => `${id++}`; const UpdateNodeInternalsFlow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); - const { project } = useReactFlow(); + const { screenToFlowPosition } = useReactFlow(); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); const onEdgeUpdate = useCallback( @@ -196,12 +196,12 @@ const UpdateNodeInternalsFlow = () => { setNodes((nds) => nds.concat({ id: getId(), - position: project({ x: evt.clientX, y: evt.clientY - 40 }), + position: screenToFlowPosition({ x: evt.clientX, y: evt.clientY }), type: 'custom', data: null, }) ), - [project, setNodes] + [screenToFlowPosition, setNodes] ); return ( diff --git a/examples/react/src/examples/UseOnSelectionChange/index.tsx b/examples/react/src/examples/UseOnSelectionChange/index.tsx index a9f91adf..b9795951 100644 --- a/examples/react/src/examples/UseOnSelectionChange/index.tsx +++ b/examples/react/src/examples/UseOnSelectionChange/index.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { ReactFlow, addEdge, @@ -10,6 +10,7 @@ import { useEdgesState, useOnSelectionChange, OnSelectionChangeParams, + OnSelectionChangeFunc, } from '@xyflow/react'; const initialNodes: Node[] = [ @@ -35,9 +36,9 @@ const initialEdges: Edge[] = [ }, ]; -const SelectionLogger = () => { +const SelectionLogger = ({ id }: { id: string }) => { const onChange = useCallback(({ nodes, edges }: OnSelectionChangeParams) => { - console.log(nodes, edges); + console.log(id, nodes, edges); }, []); useOnSelectionChange({ @@ -63,14 +64,23 @@ const Flow = () => { ); }; -const WrappedFlow = () => ( - - - -
- -
-
-); +const WrappedFlow = () => { + const [secondLoggerActive, setSecondLoggerActive] = useState(true); + + const toggleSecondLogger = () => { + setSecondLoggerActive(!secondLoggerActive); + }; + + return ( + + + + {secondLoggerActive && } +
+ +
+
+ ); +}; export default WrappedFlow; diff --git a/examples/react/src/examples/UseReactFlow/index.tsx b/examples/react/src/examples/UseReactFlow/index.tsx index 584e6e7b..69f3c762 100644 --- a/examples/react/src/examples/UseReactFlow/index.tsx +++ b/examples/react/src/examples/UseReactFlow/index.tsx @@ -57,7 +57,7 @@ const UseZoomPanHelperFlow = () => { const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); const { - project, + screenToFlowPosition, setCenter, zoomIn, zoomOut, @@ -72,9 +72,9 @@ const UseZoomPanHelperFlow = () => { const onPaneClick = useCallback( (evt: MouseEvent) => { - const projectedPosition = project({ + const projectedPosition = screenToFlowPosition({ x: evt.clientX, - y: evt.clientY - 40, + y: evt.clientY, }); setNodes((nds) => @@ -87,7 +87,7 @@ const UseZoomPanHelperFlow = () => { }) ); }, - [project, setNodes] + [screenToFlowPosition, setNodes] ); const onNodeClick = useCallback( diff --git a/examples/react/src/examples/UseUpdateNodeInternals/index.tsx b/examples/react/src/examples/UseUpdateNodeInternals/index.tsx index 7de6cb9b..371d58b6 100644 --- a/examples/react/src/examples/UseUpdateNodeInternals/index.tsx +++ b/examples/react/src/examples/UseUpdateNodeInternals/index.tsx @@ -36,20 +36,20 @@ const UpdateNodeInternalsFlow = () => { const [edges, setEdges, onEdgesChange] = useEdgesState([]); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); - const { project } = useReactFlow(); + const { screenToFlowPosition } = useReactFlow(); const onPaneClick = useCallback( (evt: MouseEvent) => setNodes((nds) => nds.concat({ id: getId(), - position: project({ x: evt.clientX, y: evt.clientY - 40 }), + position: screenToFlowPosition({ x: evt.clientX, y: evt.clientY }), data: { label: 'new node' }, targetPosition: Position.Left, sourcePosition: Position.Right, }) ), - [project, setNodes] + [screenToFlowPosition, setNodes] ); return ( diff --git a/examples/react/src/generic-tests/Flow.tsx b/examples/react/src/generic-tests/Flow.tsx new file mode 100644 index 00000000..97691bd3 --- /dev/null +++ b/examples/react/src/generic-tests/Flow.tsx @@ -0,0 +1,39 @@ +import { useState, useCallback } from 'react'; +import { + ReactFlow, + applyNodeChanges, + applyEdgeChanges, + addEdge, + OnNodesChange, + OnEdgesChange, + OnConnect, + Controls, + Panel, + MiniMap, + Background, +} from '@xyflow/react'; + +type FlowProps = { + flowConfig: FlowConfig; +}; + +export default ({ flowConfig }: FlowProps) => { + const [nodes, setNodes] = useState(flowConfig.flowProps.nodes); + const [edges, setEdges] = useState(flowConfig.flowProps.edges); + const props = { ...flowConfig.flowProps, nodes, edges }; + + const onNodesChange: OnNodesChange = useCallback((changes) => setNodes((nds) => applyNodeChanges(changes, nds)), []); + const onEdgesChange: OnEdgesChange = useCallback((changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), []); + const onConnect: OnConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []); + + return ( +
+ + {flowConfig.controlsProps && } + {flowConfig.panelProps && } + {flowConfig.minimapProps && } + {flowConfig.backgroundProps && } + +
+ ); +}; diff --git a/examples/react/src/generic-tests/edges/general.ts b/examples/react/src/generic-tests/edges/general.ts new file mode 100644 index 00000000..bb768b09 --- /dev/null +++ b/examples/react/src/generic-tests/edges/general.ts @@ -0,0 +1,163 @@ +import { MarkerType } from '@xyflow/react'; + +export default { + flowProps: { + fitView: true, + nodes: [ + { + id: '1', + data: { label: '1' }, + position: { x: 0, y: 0 }, + type: 'input', + }, + { + id: '2', + data: { label: '2' }, + position: { x: -100, y: 100 }, + }, + { + id: '3', + data: { label: '3' }, + position: { x: 100, y: 100 }, + }, + { + id: '4', + data: { label: '4' }, + position: { x: -100, y: 200 }, + }, + { + id: '5', + data: { label: '5' }, + position: { x: 100, y: 200 }, + }, + { + id: '6', + data: { label: '6' }, + position: { x: -100, y: 300 }, + }, + { + id: '7', + data: { label: '7' }, + position: { x: 100, y: 300 }, + }, + { + id: '8', + data: { label: '8' }, + position: { x: -100, y: 400 }, + }, + { + id: '9', + data: { label: '9' }, + position: { x: 100, y: 400 }, + }, + { + id: '10', + data: { label: '10' }, + position: { x: -100, y: 500 }, + }, + { + id: '11', + data: { label: '11' }, + position: { x: 100, y: 500 }, + }, + // { + // id: '12', + // data: { label: '12' }, + // position: { x: -100, y: 600 } + // }, + // { + // id: '13', + // data: { label: '13' }, + // position: { x: 100, y: 600 } + // } + ], + edges: [ + { + id: 'edge-with-class', + source: '1', + target: '2', + className: 'edge-class-test', + }, + { + id: 'edge-with-style', + source: '1', + target: '3', + style: { + stroke: 'red', + }, + }, + { + id: 'hidden-edge', + source: '2', + target: '4', + label: 'hidden', + hidden: true, + }, + { + id: 'animated-edge', + source: '3', + target: '5', + label: 'animated', + animated: true, + }, + { + id: 'not-selectable-edge', + source: '4', + target: '6', + label: 'not-selectable', + selectable: false, + }, + { + id: 'not-deletable', + source: '5', + target: '7', + label: 'not-deletable', + deletable: false, + }, + { + id: 'z-index', + source: '6', + target: '8', + label: 'z-index', + zIndex: 3141592, + }, + { + id: 'aria-label', + source: '7', + target: '9', + label: 'aria-label', + ariaLabel: 'aria-label-test', + }, + { + id: 'interaction-width', + source: '8', + target: '10', + label: 'interaction-width', + interactionWidth: 42, + }, + { + id: 'markers', + source: '9', + target: '11', + label: 'markers', + markerEnd: { type: MarkerType.Arrow }, + markerStart: { type: MarkerType.ArrowClosed }, + }, + // { + // id: 'updatable', + // source: '9', + // target: '11', + // label: 'focusable', + // updatable: true + // }, + // { + // id: 'not-focusable', + // + // source: '5', + // target: '7', + // label: 'not-focusable', + // focusable: false + // }, + ], + }, +} satisfies FlowConfig; diff --git a/examples/react/src/generic-tests/index.tsx b/examples/react/src/generic-tests/index.tsx new file mode 100644 index 00000000..1a595581 --- /dev/null +++ b/examples/react/src/generic-tests/index.tsx @@ -0,0 +1,17 @@ +import { useLocation } from 'react-router-dom'; + +import Flow from './Flow'; + +const flowConfigs = import.meta.glob('./**/*.ts', { eager: true, import: 'default' }); + +export default () => { + const location = useLocation(); + const path = `.${location.pathname.replace('/tests/generic', '')}.ts`; + const flowConfig = flowConfigs[path]; + + if (!flowConfig) { + return `404: This route doesn't exists.`; + } + + return ; +}; diff --git a/examples/react/src/generic-tests/nodes/components/DragHandleNode.tsx b/examples/react/src/generic-tests/nodes/components/DragHandleNode.tsx new file mode 100644 index 00000000..48037f61 --- /dev/null +++ b/examples/react/src/generic-tests/nodes/components/DragHandleNode.tsx @@ -0,0 +1,25 @@ +export default () => { + return ( +
+
+
+ ); +}; diff --git a/examples/react/src/generic-tests/nodes/general.ts b/examples/react/src/generic-tests/nodes/general.ts new file mode 100644 index 00000000..0fcb975c --- /dev/null +++ b/examples/react/src/generic-tests/nodes/general.ts @@ -0,0 +1,91 @@ +import DragHandleNode from './components/DragHandleNode'; + +export default { + flowProps: { + fitView: true, + nodeTypes: { + DragHandleNode, + }, + nodes: [ + { + id: 'Node-1', + data: { label: 'Node-1' }, + position: { x: 0, y: 0 }, + type: 'input', + className: 'playwright-test-class-123', + style: { backgroundColor: 'red' }, + }, + { + id: 'Node-2', + type: 'output', + data: { label: 'Node-2' }, + position: { x: -100, y: 100 }, + }, + { + id: 'Node-3', + data: { label: 'Node-3' }, + position: { x: 100, y: 100 }, + }, + { + id: 'Node-4', + data: { label: 'Node-4' }, + position: { x: 0, y: 200 }, + type: 'output', + }, + { + id: 'drag-handle', + data: { label: 'Drag Handle' }, + position: { x: 200, y: 0 }, + type: 'DragHandleNode', + dragHandle: '.custom-drag-handle', + }, + { + id: 'notConnectable', + type: 'output', + data: { label: 'notConnectable' }, + position: { x: 0, y: 300 }, + connectable: false, + }, + { + id: 'notDraggable', + data: { label: 'notDraggable' }, + position: { x: 0, y: 400 }, + draggable: false, + }, + { + id: 'notSelectable', + data: { label: 'notSelectable' }, + position: { x: 0, y: 500 }, + selectable: false, + }, + { + id: 'notDeletable', + data: { label: 'notDeletable' }, + position: { x: 0, y: 600 }, + deletable: false, + }, + { + id: 'hidden', + data: { label: 'hidden' }, + position: { x: 0, y: 700 }, + hidden: true, + }, + ], + edges: [ + { + id: '1-2', + type: 'default', + source: 'Node-1', + target: 'Node-2', + label: 'edge', + }, + { + id: '1-3', + type: 'default', + source: 'Node-1', + target: 'Node-3', + label: 'edge', + }, + ], + }, +} satisfies FlowConfig; diff --git a/examples/react/src/generic-tests/pane/general.ts b/examples/react/src/generic-tests/pane/general.ts new file mode 100644 index 00000000..54c1d193 --- /dev/null +++ b/examples/react/src/generic-tests/pane/general.ts @@ -0,0 +1,37 @@ +export default { + flowProps: { + minZoom: 0.25, + maxZoom: 4, + fitView: true, + nodes: [ + { + id: '1', + data: { label: '1' }, + position: { x: 0, y: 0 }, + type: 'input', + }, + { + id: '2', + data: { label: '2' }, + position: { x: -100, y: 100 }, + }, + { + id: '3', + data: { label: '3' }, + position: { x: 100, y: 100 }, + }, + ], + edges: [ + { + id: 'first-edge', + source: '1', + target: '2', + }, + { + id: 'second-edge', + source: '1', + target: '3', + }, + ], + }, +} satisfies FlowConfig; diff --git a/examples/react/src/generic-tests/pane/non-defaults.ts b/examples/react/src/generic-tests/pane/non-defaults.ts new file mode 100644 index 00000000..1da85cdd --- /dev/null +++ b/examples/react/src/generic-tests/pane/non-defaults.ts @@ -0,0 +1,38 @@ +export default { + flowProps: { + panOnScroll: true, + defaultViewport: { x: 1.23, y: 9.87, zoom: 1.234 }, + autoPanOnConnect: false, + autoPanOnNodeDrag: false, + nodes: [ + { + id: '1', + data: { label: '1' }, + position: { x: 0, y: 0 }, + type: 'input', + }, + { + id: '2', + data: { label: '2' }, + position: { x: -100, y: 100 }, + }, + { + id: '3', + data: { label: '3' }, + position: { x: 100, y: 100 }, + }, + ], + edges: [ + { + id: 'first-edge', + source: '1', + target: '2', + }, + { + id: 'second-edge', + source: '1', + target: '3', + }, + ], + }, +} satisfies FlowConfig; diff --git a/examples/react/src/main.tsx b/examples/react/src/main.tsx index 34599efc..663a29b5 100644 --- a/examples/react/src/main.tsx +++ b/examples/react/src/main.tsx @@ -1,9 +1,10 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; + import App from './App'; -import './index.css'; import '@xyflow/react/dist/style.css'; +import './index.css'; createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/examples/svelte/package.json b/examples/svelte/package.json index 8cd6a14e..2679983a 100644 --- a/examples/svelte/package.json +++ b/examples/svelte/package.json @@ -1,5 +1,5 @@ { - "name": "svelte", + "name": "svelte-examples", "version": "0.0.1", "private": true, "scripts": { @@ -12,20 +12,20 @@ "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { - "@sveltejs/adapter-auto": "^2.0.0", - "@sveltejs/kit": "^1.20.4", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-svelte": "^2.30.0", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "svelte": "^4.0.5", - "svelte-check": "^3.4.3", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^4.4.2" + "@sveltejs/adapter-auto": "^2.1.1", + "@sveltejs/kit": "^1.27.3", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "eslint": "^8.53.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-svelte": "^2.35.0", + "prettier": "^3.0.3", + "prettier-plugin-svelte": "^3.0.3", + "svelte": "^4.2.2", + "svelte-check": "^3.5.2", + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "vite": "^4.5.0" }, "type": "module", "dependencies": { diff --git a/examples/svelte/src/app.d.ts b/examples/svelte/src/app.d.ts index f59b884c..3e18db2a 100644 --- a/examples/svelte/src/app.d.ts +++ b/examples/svelte/src/app.d.ts @@ -1,4 +1,15 @@ // See https://kit.svelte.dev/docs/types#app + +import type { + BackgroundProps, + Edge, + MiniMap, + MiniMapProps, + Node, + PanelProps, + SvelteFlowProps +} from '@xyflow/svelte'; + // for information about these interfaces declare global { namespace App { @@ -7,6 +18,14 @@ declare global { // interface PageData {} // interface Platform {} } + + interface FlowConfig { + flowProps: Omit & { nodes: Node[]; edges: Edge[] }; + panelProps: PanelProps; + backgroundProps: BackgroundProps; + controlsProps: ControlsProps; + minimapProps: MiniMapProps; + } } export {}; diff --git a/examples/svelte/src/app.html b/examples/svelte/src/app.html index 68f1308a..a9d5b2f0 100644 --- a/examples/svelte/src/app.html +++ b/examples/svelte/src/app.html @@ -1,8 +1,9 @@ - + + diff --git a/examples/svelte/src/generic-tests/nodes/general.ts b/examples/svelte/src/generic-tests/nodes/general.ts new file mode 100644 index 00000000..4e5abc91 --- /dev/null +++ b/examples/svelte/src/generic-tests/nodes/general.ts @@ -0,0 +1,91 @@ +import DragHandleNode from './components/DragHandleNode.svelte'; + +export default { + flowProps: { + fitView: true, + nodeTypes: { + DragHandleNode + }, + nodes: [ + { + id: 'Node-1', + data: { label: 'Node-1' }, + position: { x: 0, y: 0 }, + type: 'input', + class: 'playwright-test-class-123', + style: 'background-color: red;' + }, + { + id: 'Node-2', + type: 'output', + data: { label: 'Node-2' }, + position: { x: -100, y: 100 } + }, + { + id: 'Node-3', + data: { label: 'Node-3' }, + position: { x: 100, y: 100 } + }, + { + id: 'Node-4', + data: { label: 'Node-4' }, + position: { x: 0, y: 200 }, + type: 'output' + }, + { + id: 'drag-handle', + data: { label: 'Drag Handle' }, + position: { x: 200, y: 0 }, + type: 'DragHandleNode', + dragHandle: '.custom-drag-handle' + }, + { + id: 'notConnectable', + type: 'output', + data: { label: 'notConnectable' }, + position: { x: 0, y: 300 }, + connectable: false + }, + { + id: 'notDraggable', + data: { label: 'notDraggable' }, + position: { x: 0, y: 400 }, + draggable: false + }, + { + id: 'notSelectable', + data: { label: 'notSelectable' }, + position: { x: 0, y: 500 }, + selectable: false + }, + { + id: 'notDeletable', + data: { label: 'notDeletable' }, + position: { x: 0, y: 600 }, + deletable: false + }, + { + id: 'hidden', + data: { label: 'hidden' }, + position: { x: 0, y: 700 }, + hidden: true + } + ], + edges: [ + { + id: '1-2', + type: 'default', + source: 'Node-1', + target: 'Node-2', + label: 'edge' + }, + { + id: '1-3', + type: 'default', + source: 'Node-1', + target: 'Node-3', + label: 'edge' + } + ] + } +} satisfies FlowConfig; diff --git a/examples/svelte/src/generic-tests/pane/activation-keys.ts b/examples/svelte/src/generic-tests/pane/activation-keys.ts new file mode 100644 index 00000000..ca16c47b --- /dev/null +++ b/examples/svelte/src/generic-tests/pane/activation-keys.ts @@ -0,0 +1,40 @@ +export default { + flowProps: { + zoomOnScroll: false, + // zoomActivationKey: 'Space', + // panOnDrag: false, + // panOnScroll: false, + panActivationKey: 'Space', + fitView: true, + nodes: [ + { + id: '1', + data: { label: '1' }, + position: { x: 0, y: 0 }, + type: 'input' + }, + { + id: '2', + data: { label: '2' }, + position: { x: -100, y: 100 } + }, + { + id: '3', + data: { label: '3' }, + position: { x: 100, y: 100 } + } + ], + edges: [ + { + id: 'first-edge', + source: '1', + target: '2' + }, + { + id: 'second-edge', + source: '1', + target: '3' + } + ] + } +} satisfies FlowConfig; diff --git a/examples/svelte/src/generic-tests/pane/general.ts b/examples/svelte/src/generic-tests/pane/general.ts new file mode 100644 index 00000000..9b9c9a5c --- /dev/null +++ b/examples/svelte/src/generic-tests/pane/general.ts @@ -0,0 +1,37 @@ +export default { + flowProps: { + minZoom: 0.25, + maxZoom: 4, + fitView: true, + nodes: [ + { + id: '1', + data: { label: '1' }, + position: { x: 0, y: 0 }, + type: 'input' + }, + { + id: '2', + data: { label: '2' }, + position: { x: -100, y: 100 } + }, + { + id: '3', + data: { label: '3' }, + position: { x: 100, y: 100 } + } + ], + edges: [ + { + id: 'first-edge', + source: '1', + target: '2' + }, + { + id: 'second-edge', + source: '1', + target: '3' + } + ] + } +} satisfies FlowConfig; diff --git a/examples/svelte/src/generic-tests/pane/non-defaults.ts b/examples/svelte/src/generic-tests/pane/non-defaults.ts new file mode 100644 index 00000000..d3aa8e27 --- /dev/null +++ b/examples/svelte/src/generic-tests/pane/non-defaults.ts @@ -0,0 +1,38 @@ +export default { + flowProps: { + panOnScroll: true, + initialViewport: { x: 1.23, y: 9.87, zoom: 1.234 }, + autoPanOnConnect: false, + autoPanOnNodeDrag: false, + nodes: [ + { + id: '1', + data: { label: '1' }, + position: { x: 0, y: 0 }, + type: 'input' + }, + { + id: '2', + data: { label: '2' }, + position: { x: -100, y: 100 } + }, + { + id: '3', + data: { label: '3' }, + position: { x: 100, y: 100 } + } + ], + edges: [ + { + id: 'first-edge', + source: '1', + target: '2' + }, + { + id: 'second-edge', + source: '1', + target: '3' + } + ] + } +} satisfies FlowConfig; diff --git a/examples/svelte/src/routes/+layout.server.ts b/examples/svelte/src/routes/+layout.server.ts index b64f1170..c968a610 100644 --- a/examples/svelte/src/routes/+layout.server.ts +++ b/examples/svelte/src/routes/+layout.server.ts @@ -2,7 +2,7 @@ import { redirect } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ route }) { - if (route.id === '/') { - throw redirect(307, '/overview'); - } + if (route.id === '/') { + throw redirect(307, '/examples/overview'); + } } diff --git a/examples/svelte/src/routes/+layout.svelte b/examples/svelte/src/routes/examples/+layout.svelte similarity index 81% rename from examples/svelte/src/routes/+layout.svelte rename to examples/svelte/src/routes/examples/+layout.svelte index 84598fec..cf9f655f 100644 --- a/examples/svelte/src/routes/+layout.svelte +++ b/examples/svelte/src/routes/examples/+layout.svelte @@ -1,5 +1,5 @@
diff --git a/examples/svelte/src/routes/examples/+page.svelte b/examples/svelte/src/routes/examples/+page.svelte new file mode 100644 index 00000000..45db2228 --- /dev/null +++ b/examples/svelte/src/routes/examples/+page.svelte @@ -0,0 +1 @@ +
this redirects to /overview
diff --git a/examples/svelte/src/routes/add-node-on-drop/+page.svelte b/examples/svelte/src/routes/examples/add-node-on-drop/+page.svelte similarity index 100% rename from examples/svelte/src/routes/add-node-on-drop/+page.svelte rename to examples/svelte/src/routes/examples/add-node-on-drop/+page.svelte diff --git a/examples/svelte/src/routes/add-node-on-drop/Flow.svelte b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte similarity index 100% rename from examples/svelte/src/routes/add-node-on-drop/Flow.svelte rename to examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte diff --git a/examples/svelte/src/routes/custom-connection-line/+page.svelte b/examples/svelte/src/routes/examples/custom-connection-line/+page.svelte similarity index 100% rename from examples/svelte/src/routes/custom-connection-line/+page.svelte rename to examples/svelte/src/routes/examples/custom-connection-line/+page.svelte diff --git a/examples/svelte/src/routes/custom-connection-line/ConnectionLine.svelte b/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte similarity index 100% rename from examples/svelte/src/routes/custom-connection-line/ConnectionLine.svelte rename to examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte diff --git a/examples/svelte/src/routes/custom-connection-line/CustomNode.svelte b/examples/svelte/src/routes/examples/custom-connection-line/CustomNode.svelte similarity index 100% rename from examples/svelte/src/routes/custom-connection-line/CustomNode.svelte rename to examples/svelte/src/routes/examples/custom-connection-line/CustomNode.svelte diff --git a/examples/svelte/src/routes/customnode/+page.svelte b/examples/svelte/src/routes/examples/customnode/+page.svelte similarity index 100% rename from examples/svelte/src/routes/customnode/+page.svelte rename to examples/svelte/src/routes/examples/customnode/+page.svelte diff --git a/examples/svelte/src/routes/customnode/CustomNode.svelte b/examples/svelte/src/routes/examples/customnode/CustomNode.svelte similarity index 100% rename from examples/svelte/src/routes/customnode/CustomNode.svelte rename to examples/svelte/src/routes/examples/customnode/CustomNode.svelte diff --git a/examples/svelte/src/routes/dagre/+page.svelte b/examples/svelte/src/routes/examples/dagre/+page.svelte similarity index 100% rename from examples/svelte/src/routes/dagre/+page.svelte rename to examples/svelte/src/routes/examples/dagre/+page.svelte diff --git a/examples/svelte/src/routes/dagre/nodes-and-edges.ts b/examples/svelte/src/routes/examples/dagre/nodes-and-edges.ts similarity index 100% rename from examples/svelte/src/routes/dagre/nodes-and-edges.ts rename to examples/svelte/src/routes/examples/dagre/nodes-and-edges.ts diff --git a/examples/svelte/src/routes/drag-n-drop/+page.svelte b/examples/svelte/src/routes/examples/drag-n-drop/+page.svelte similarity index 100% rename from examples/svelte/src/routes/drag-n-drop/+page.svelte rename to examples/svelte/src/routes/examples/drag-n-drop/+page.svelte diff --git a/examples/svelte/src/routes/drag-n-drop/Flow.svelte b/examples/svelte/src/routes/examples/drag-n-drop/Flow.svelte similarity index 92% rename from examples/svelte/src/routes/drag-n-drop/Flow.svelte rename to examples/svelte/src/routes/examples/drag-n-drop/Flow.svelte index 661b7f5e..2826a8ea 100644 --- a/examples/svelte/src/routes/drag-n-drop/Flow.svelte +++ b/examples/svelte/src/routes/examples/drag-n-drop/Flow.svelte @@ -69,10 +69,10 @@ return null; } - const type = event.dataTransfer.getData('application/svelteflow'); - const position = svelteFlow.project({ + const type = event.dataTransfer.getData('application/svelteflow') || 'default'; + const position = svelteFlow.screenToFlowPosition({ x: event.clientX, - y: event.clientY - 40 + y: event.clientY }); const newNode: Node = { id: `${Math.random()}`, @@ -81,7 +81,7 @@ data: { label: `${type} node` } }; - svelteFlow.nodes.update((nds) => nds.concat(newNode)); + nodes.update((nds) => nds.concat(newNode)); }; $: { diff --git a/examples/svelte/src/routes/drag-n-drop/Sidebar.svelte b/examples/svelte/src/routes/examples/drag-n-drop/Sidebar.svelte similarity index 100% rename from examples/svelte/src/routes/drag-n-drop/Sidebar.svelte rename to examples/svelte/src/routes/examples/drag-n-drop/Sidebar.svelte diff --git a/examples/svelte/src/routes/edges/+page.svelte b/examples/svelte/src/routes/examples/edges/+page.svelte similarity index 100% rename from examples/svelte/src/routes/edges/+page.svelte rename to examples/svelte/src/routes/examples/edges/+page.svelte diff --git a/examples/svelte/src/routes/figma/+page.svelte b/examples/svelte/src/routes/examples/figma/+page.svelte similarity index 98% rename from examples/svelte/src/routes/figma/+page.svelte rename to examples/svelte/src/routes/examples/figma/+page.svelte index 7ebdd500..4673eeec 100644 --- a/examples/svelte/src/routes/figma/+page.svelte +++ b/examples/svelte/src/routes/examples/figma/+page.svelte @@ -45,6 +45,7 @@ {edges} fitView selectionMode={SelectionMode.Partial} + selectionOnDrag panOnScroll {panOnDrag} {onMoveStart} diff --git a/examples/svelte/src/routes/interaction/+page.svelte b/examples/svelte/src/routes/examples/interaction/+page.svelte similarity index 100% rename from examples/svelte/src/routes/interaction/+page.svelte rename to examples/svelte/src/routes/examples/interaction/+page.svelte diff --git a/examples/svelte/src/routes/intersections/+page.svelte b/examples/svelte/src/routes/examples/intersections/+page.svelte similarity index 100% rename from examples/svelte/src/routes/intersections/+page.svelte rename to examples/svelte/src/routes/examples/intersections/+page.svelte diff --git a/examples/svelte/src/routes/intersections/+server.ts b/examples/svelte/src/routes/examples/intersections/+server.ts similarity index 100% rename from examples/svelte/src/routes/intersections/+server.ts rename to examples/svelte/src/routes/examples/intersections/+server.ts diff --git a/examples/svelte/src/routes/intersections/Flow.svelte b/examples/svelte/src/routes/examples/intersections/Flow.svelte similarity index 100% rename from examples/svelte/src/routes/intersections/Flow.svelte rename to examples/svelte/src/routes/examples/intersections/Flow.svelte diff --git a/examples/svelte/src/routes/intersections/nodes-and-edges.ts b/examples/svelte/src/routes/examples/intersections/nodes-and-edges.ts similarity index 100% rename from examples/svelte/src/routes/intersections/nodes-and-edges.ts rename to examples/svelte/src/routes/examples/intersections/nodes-and-edges.ts diff --git a/examples/svelte/src/routes/overview/+page.svelte b/examples/svelte/src/routes/examples/overview/+page.svelte similarity index 93% rename from examples/svelte/src/routes/overview/+page.svelte rename to examples/svelte/src/routes/examples/overview/+page.svelte index 32bef5d0..dddbf398 100644 --- a/examples/svelte/src/routes/overview/+page.svelte +++ b/examples/svelte/src/routes/examples/overview/+page.svelte @@ -83,6 +83,12 @@ type: 'custom', data: { label: 'Custom Node' }, position: { x: 150, y: 300 } + }, + { + id: 'hideunhide', + data: { label: 'HIDE ME' }, + position: { x: 300, y: 75 }, + hidden: true } ]); @@ -179,6 +185,13 @@ + diff --git a/examples/svelte/src/routes/overview/CustomEdge.svelte b/examples/svelte/src/routes/examples/overview/CustomEdge.svelte similarity index 100% rename from examples/svelte/src/routes/overview/CustomEdge.svelte rename to examples/svelte/src/routes/examples/overview/CustomEdge.svelte diff --git a/examples/svelte/src/routes/overview/CustomNode.svelte b/examples/svelte/src/routes/examples/overview/CustomNode.svelte similarity index 100% rename from examples/svelte/src/routes/overview/CustomNode.svelte rename to examples/svelte/src/routes/examples/overview/CustomNode.svelte diff --git a/examples/svelte/src/routes/overview/CustomNodeDragHandle.svelte b/examples/svelte/src/routes/examples/overview/CustomNodeDragHandle.svelte similarity index 100% rename from examples/svelte/src/routes/overview/CustomNodeDragHandle.svelte rename to examples/svelte/src/routes/examples/overview/CustomNodeDragHandle.svelte diff --git a/examples/svelte/src/routes/stress/+page.svelte b/examples/svelte/src/routes/examples/stress/+page.svelte similarity index 100% rename from examples/svelte/src/routes/stress/+page.svelte rename to examples/svelte/src/routes/examples/stress/+page.svelte diff --git a/examples/svelte/src/routes/subflows/+page.svelte b/examples/svelte/src/routes/examples/subflows/+page.svelte similarity index 100% rename from examples/svelte/src/routes/subflows/+page.svelte rename to examples/svelte/src/routes/examples/subflows/+page.svelte diff --git a/examples/svelte/src/routes/subflows/DebugNode.svelte b/examples/svelte/src/routes/examples/subflows/DebugNode.svelte similarity index 100% rename from examples/svelte/src/routes/subflows/DebugNode.svelte rename to examples/svelte/src/routes/examples/subflows/DebugNode.svelte diff --git a/examples/svelte/src/routes/two-way-viewport/+page.svelte b/examples/svelte/src/routes/examples/two-way-viewport/+page.svelte similarity index 100% rename from examples/svelte/src/routes/two-way-viewport/+page.svelte rename to examples/svelte/src/routes/examples/two-way-viewport/+page.svelte diff --git a/examples/svelte/src/routes/two-way-viewport/Flow.svelte b/examples/svelte/src/routes/examples/two-way-viewport/Flow.svelte similarity index 100% rename from examples/svelte/src/routes/two-way-viewport/Flow.svelte rename to examples/svelte/src/routes/examples/two-way-viewport/Flow.svelte diff --git a/examples/svelte/src/routes/usesvelteflow/+page.svelte b/examples/svelte/src/routes/examples/usesvelteflow/+page.svelte similarity index 100% rename from examples/svelte/src/routes/usesvelteflow/+page.svelte rename to examples/svelte/src/routes/examples/usesvelteflow/+page.svelte diff --git a/examples/svelte/src/routes/usesvelteflow/Flow.svelte b/examples/svelte/src/routes/examples/usesvelteflow/Flow.svelte similarity index 100% rename from examples/svelte/src/routes/usesvelteflow/Flow.svelte rename to examples/svelte/src/routes/examples/usesvelteflow/Flow.svelte diff --git a/examples/svelte/src/routes/usesvelteflow/Sidebar.svelte b/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte similarity index 100% rename from examples/svelte/src/routes/usesvelteflow/Sidebar.svelte rename to examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte diff --git a/examples/svelte/src/routes/useupdatenodeinternals/+page.svelte b/examples/svelte/src/routes/examples/useupdatenodeinternals/+page.svelte similarity index 100% rename from examples/svelte/src/routes/useupdatenodeinternals/+page.svelte rename to examples/svelte/src/routes/examples/useupdatenodeinternals/+page.svelte diff --git a/examples/svelte/src/routes/useupdatenodeinternals/CustomNode.svelte b/examples/svelte/src/routes/examples/useupdatenodeinternals/CustomNode.svelte similarity index 100% rename from examples/svelte/src/routes/useupdatenodeinternals/CustomNode.svelte rename to examples/svelte/src/routes/examples/useupdatenodeinternals/CustomNode.svelte diff --git a/examples/svelte/src/routes/useupdatenodeinternals/Flow.svelte b/examples/svelte/src/routes/examples/useupdatenodeinternals/Flow.svelte similarity index 100% rename from examples/svelte/src/routes/useupdatenodeinternals/Flow.svelte rename to examples/svelte/src/routes/examples/useupdatenodeinternals/Flow.svelte diff --git a/examples/svelte/src/routes/validation/+page.svelte b/examples/svelte/src/routes/examples/validation/+page.svelte similarity index 100% rename from examples/svelte/src/routes/validation/+page.svelte rename to examples/svelte/src/routes/examples/validation/+page.svelte diff --git a/examples/svelte/src/routes/validation/style.css b/examples/svelte/src/routes/examples/validation/style.css similarity index 100% rename from examples/svelte/src/routes/validation/style.css rename to examples/svelte/src/routes/examples/validation/style.css diff --git a/examples/svelte/src/routes/tests/generic/[topic]/[example]/+page.svelte b/examples/svelte/src/routes/tests/generic/[topic]/[example]/+page.svelte new file mode 100644 index 00000000..8d75afb8 --- /dev/null +++ b/examples/svelte/src/routes/tests/generic/[topic]/[example]/+page.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/examples/svelte/src/routes/tests/generic/[topic]/[example]/+page.ts b/examples/svelte/src/routes/tests/generic/[topic]/[example]/+page.ts new file mode 100644 index 00000000..193516c1 --- /dev/null +++ b/examples/svelte/src/routes/tests/generic/[topic]/[example]/+page.ts @@ -0,0 +1,19 @@ +import { error } from '@sveltejs/kit'; + +const flowConfigs = import.meta.glob('/src/generic-tests/**/*.ts', { + eager: true, + import: 'default' +}); + +/** @type {import('./$types').PageLoad} */ +export function load({ params }) { + const flowConfig = flowConfigs[`/src/generic-tests/${params.topic}/${params.example}.ts`]; + + if (!flowConfig) { + throw error(404, 'Not found'); + } + + return { + flowConfig + }; +} diff --git a/examples/svelte/src/routes/tests/generic/[topic]/[example]/Flow.svelte b/examples/svelte/src/routes/tests/generic/[topic]/[example]/Flow.svelte new file mode 100644 index 00000000..279005a0 --- /dev/null +++ b/examples/svelte/src/routes/tests/generic/[topic]/[example]/Flow.svelte @@ -0,0 +1,29 @@ + + + + {#if flowConfig.panelProps} + + {/if} + {#if flowConfig.minimapProps} + + {/if} + {#if flowConfig.controlsProps} + + {/if} + {#if flowConfig.backgroundProps} + + {/if} + diff --git a/examples/svelte/static/favicon.ico b/examples/svelte/static/favicon.ico new file mode 100644 index 00000000..9ef0fbfd Binary files /dev/null and b/examples/svelte/static/favicon.ico differ diff --git a/examples/svelte/static/favicon.png b/examples/svelte/static/favicon.png deleted file mode 100644 index 825b9e65..00000000 Binary files a/examples/svelte/static/favicon.png and /dev/null differ diff --git a/examples/svelte/svelte.config.js b/examples/svelte/svelte.config.js index 1cf26a00..80719d79 100644 --- a/examples/svelte/svelte.config.js +++ b/examples/svelte/svelte.config.js @@ -11,7 +11,10 @@ const config = { // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() + adapter: adapter(), + alias: { + $components: './src/components' + } } }; diff --git a/package.json b/package.json index f0e22afe..4411e137 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "dev": "turbo run dev --parallel --concurrency 12", "dev:svelte": "turbo run dev --filter=svelte --filter=system", "dev:react": "turbo run dev --filter=react", + "test:svelte": "pnpm --filter=playwright run test:svelte", + "test:svelte:ui": "pnpm --filter=playwright run test:svelte:ui", + "test:react": "pnpm --filter=playwright run test:react", + "test:react:ui": "pnpm --filter=playwright run test:react:ui", "build": "turbo run build", "test": "turbo run test", "lint": "turbo run lint", diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index 9109a910..49e02573 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -3,7 +3,7 @@ import { memo, useEffect, useRef, type MouseEvent, useCallback } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; -import { getRectOfNodes, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system'; +import { getNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; import Panel from '../../components/Panel'; @@ -25,7 +25,7 @@ const selector = (s: ReactFlowState) => { return { viewBB, - boundingRect: s.nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(s.nodes, s.nodeOrigin), viewBB) : viewBB, + boundingRect: s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, s.nodeOrigin), viewBB) : viewBB, rfId: s.rfId, nodeOrigin: s.nodeOrigin, panZoom: s.panZoom, diff --git a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx index 8710f88a..fc0c309f 100644 --- a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx +++ b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx @@ -1,7 +1,7 @@ import { useCallback, CSSProperties } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; -import { getRectOfNodes, Transform, Rect, Position, internalsSymbol } from '@xyflow/system'; +import { getNodesBounds, Transform, Rect, Position, internalsSymbol } from '@xyflow/system'; import { Node, ReactFlowState } from '../../types'; import { useStore } from '../../hooks/useStore'; @@ -105,7 +105,7 @@ function NodeToolbar({ return null; } - const nodeRect: Rect = getRectOfNodes(nodes, nodeOrigin); + const nodeRect: Rect = getNodesBounds(nodes, nodeOrigin); const zIndex: number = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1)); const wrapperStyle: CSSProperties = { diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx index ad1d8f2a..750254ff 100644 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ b/packages/react/src/components/Edges/wrapEdge.tsx @@ -213,6 +213,7 @@ export default (EdgeComponent: ComponentType) => { onKeyDown={isFocusable ? onKeyDown : undefined} tabIndex={isFocusable ? 0 : undefined} role={isFocusable ? 'button' : 'img'} + data-id={id} data-testid={`rf__edge-${id}`} aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`} aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined} diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index f22c35ee..ec88fccd 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -6,7 +6,7 @@ import { memo, useRef, useEffect, type MouseEvent, type KeyboardEvent } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; -import { getRectOfNodes } from '@xyflow/system'; +import { getNodesBounds } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; import useDrag from '../../hooks/useDrag'; @@ -22,7 +22,7 @@ export type NodesSelectionProps = { const selector = (s: ReactFlowState) => { const selectedNodes = s.nodes.filter((n) => n.selected); - const { width, height, x, y } = getRectOfNodes(selectedNodes, s.nodeOrigin); + const { width, height, x, y } = getNodesBounds(selectedNodes, s.nodeOrigin); return { width, diff --git a/packages/react/src/components/SelectionListener/index.tsx b/packages/react/src/components/SelectionListener/index.tsx index 92644f85..b71231f6 100644 --- a/packages/react/src/components/SelectionListener/index.tsx +++ b/packages/react/src/components/SelectionListener/index.tsx @@ -32,8 +32,9 @@ const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) = useEffect(() => { const params = { nodes: selectedNodes, edges: selectedEdges }; + onSelectionChange?.(params); - store.getState().onSelectionChange?.(params); + store.getState().onSelectionChangeHandlers.forEach((fn) => fn(params)); }, [selectedNodes, selectedEdges, onSelectionChange]); return null; @@ -41,12 +42,12 @@ const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) = SelectionListener.displayName = 'SelectionListener'; -const changeSelector = (s: ReactFlowState) => !!s.onSelectionChange; +const changeSelector = (s: ReactFlowState) => !!s.onSelectionChangeHandlers; function Wrapper({ onSelectionChange }: SelectionListenerProps) { - const storeHasSelectionChange = useStore(changeSelector); + const storeHasSelectionChangeHandlers = useStore(changeSelector); - if (onSelectionChange || storeHasSelectionChange) { + if (onSelectionChange || storeHasSelectionChangeHandlers) { return ; } diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index 442cc2bf..bb92f66a 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -50,7 +50,7 @@ const FlowRenderer = ({ elementsSelectable, zoomOnScroll, zoomOnPinch, - panOnScroll, + panOnScroll: _panOnScroll, panOnScrollSpeed, panOnScrollMode, zoomOnDoubleClick, @@ -72,6 +72,7 @@ const FlowRenderer = ({ const panActivationKeyPressed = useKeyPress(panActivationKeyCode); const panOnDrag = panActivationKeyPressed || _panOnDrag; + const panOnScroll = panActivationKeyPressed || _panOnScroll; const isSelecting = selectionKeyPressed || (selectionOnDrag && panOnDrag !== true); useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode }); diff --git a/packages/react/src/hooks/useOnSelectionChange.ts b/packages/react/src/hooks/useOnSelectionChange.ts index 323f23a8..51df3ddb 100644 --- a/packages/react/src/hooks/useOnSelectionChange.ts +++ b/packages/react/src/hooks/useOnSelectionChange.ts @@ -4,14 +4,20 @@ import { useStoreApi } from './useStore'; import type { OnSelectionChangeFunc } from '../types'; export type UseOnSelectionChangeOptions = { - onChange?: OnSelectionChangeFunc; + onChange: OnSelectionChangeFunc; }; function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { const store = useStoreApi(); useEffect(() => { - store.setState({ onSelectionChange: onChange }); + const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange]; + store.setState({ onSelectionChangeHandlers: nextOnSelectionChangeHandlers }); + + return () => { + const nextHandlers = store.getState().onSelectionChangeHandlers.filter((fn) => fn !== onChange); + store.setState({ onSelectionChangeHandlers: nextHandlers }); + }; }, [onChange]); } diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index 1872d414..f1099b43 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { pointToRendererPoint, - getTransformForBounds, + getViewportForBounds, fitView, type XYPosition, rendererPointToPoint, @@ -76,50 +76,40 @@ const useViewportHelper = (): ViewportHelperFunctions => { }, fitBounds: (bounds, options) => { const { width, height, minZoom, maxZoom, panZoom } = store.getState(); - const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1); + const viewport = getViewportForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1); - panZoom?.setViewport( - { - x, - y, - zoom, - }, - { duration: options?.duration } - ); + panZoom?.setViewport(viewport, { duration: options?.duration }); }, - project: (position: XYPosition) => { - const { transform, snapToGrid, snapGrid } = store.getState(); - return pointToRendererPoint(position, transform, snapToGrid, snapGrid); - }, - screenToFlowCoordinate: (position: XYPosition) => { + screenToFlowPosition: (position: XYPosition) => { const { transform, snapToGrid, snapGrid, domNode } = store.getState(); - if (domNode) { - const { x: domX, y: domY } = domNode.getBoundingClientRect(); - const correctedPosition = { - x: position.x - domX, - y: position.y - domY, - }; - - return pointToRendererPoint(correctedPosition, transform, snapToGrid, snapGrid || [1, 1]); + if (!domNode) { + return position; } - return { x: 0, y: 0 }; + const { x: domX, y: domY } = domNode.getBoundingClientRect(); + + const correctedPosition = { + x: position.x - domX, + y: position.y - domY, + }; + + return pointToRendererPoint(correctedPosition, transform, snapToGrid, snapGrid || [1, 1]); }, - flowToScreenCoordinate: (position: XYPosition) => { + flowToScreenPosition: (position: XYPosition) => { const { transform, domNode } = store.getState(); - if (domNode) { - const { x: domX, y: domY } = domNode.getBoundingClientRect(); - const rendererPosition = rendererPointToPoint(position, transform); - - return { - x: rendererPosition.x + domX, - y: rendererPosition.y + domY, - }; + if (!domNode) { + return position; } - return { x: 0, y: 0 }; + const { x: domX, y: domY } = domNode.getBoundingClientRect(); + const rendererPosition = rendererPointToPoint(position, transform); + + return { + x: rendererPosition.x + domX, + y: rendererPosition.y + domY, + }; }, viewportInitialized: panZoomInitialized, }; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 37a6bc74..be3de1b5 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -8,7 +8,7 @@ export { default as SimpleBezierEdge, getSimpleBezierPath } from './components/E export { default as SmoothStepEdge } from './components/Edges/SmoothStepEdge'; export { default as BaseEdge } from './components/Edges/BaseEdge'; export { default as ReactFlowProvider } from './components/ReactFlowProvider'; -export { default as Panel } from './components/Panel'; +export { default as Panel, type PanelProps } from './components/Panel'; export { default as EdgeLabelRenderer } from './components/EdgeLabelRenderer'; export { default as useReactFlow } from './hooks/useReactFlow'; @@ -85,6 +85,6 @@ export { getSmoothStepPath, type GetStraightPathParams, getStraightPath, - getTransformForBounds, - getRectOfNodes, + getViewportForBounds, + getNodesBounds, } from '@xyflow/system'; diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index b87310f9..8f8fa450 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -2,8 +2,8 @@ import { infiniteExtent, ConnectionMode, updateNodes, - getRectOfNodes, - getTransformForBounds, + getNodesBounds, + getViewportForBounds, Transform, } from '@xyflow/system'; @@ -32,8 +32,9 @@ const getInitialState = ({ width: node.size?.width, height: node.size?.height, })); - const bounds = getRectOfNodes(nodesWithDimensions, [0, 0]); - transform = getTransformForBounds(bounds, width, height, 0.5, 2, 0.1); + const bounds = getNodesBounds(nodesWithDimensions, [0, 0]); + const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); + transform = [x, y, zoom]; } return { @@ -92,6 +93,7 @@ const getInitialState = ({ connectionRadius: 20, onError: () => null, isValidConnection: undefined, + onSelectionChangeHandlers: [], lib: 'react', }; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 2b2945a6..225e159c 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -11,7 +11,7 @@ import { GetViewport, SetCenter, FitBounds, - Project, + XYPosition, } from '@xyflow/system'; import type { NodeChange, EdgeChange, Node, WrapNodeProps, Edge, EdgeProps, WrapEdgeProps, ReactFlowInstance } from '.'; @@ -54,8 +54,7 @@ export type ViewportHelperFunctions = { fitView: FitView; setCenter: SetCenter; fitBounds: FitBounds; - project: Project; - screenToFlowCoordinate: Project; - flowToScreenCoordinate: Project; + screenToFlowPosition: (position: XYPosition) => XYPosition; + flowToScreenPosition: (position: XYPosition) => XYPosition; viewportInitialized: boolean; }; diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index f79cb874..3662f862 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -124,7 +124,7 @@ export type ReactFlowStore = { onViewportChange?: OnViewportChange; onViewportChangeEnd?: OnViewportChange; - onSelectionChange?: OnSelectionChangeFunc; + onSelectionChangeHandlers: OnSelectionChangeFunc[]; ariaLiveMessage: string; autoPanOnConnect: boolean; diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 6a3a9d15..f32356f5 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.0.27 + +- add `selectionOnDrag` prop - can be used to create figma-like controls in combination with `panOnDrag={false}` / `panOnDrag={[1, 2]}` + `panOnScroll={true}` +- ⚠️ rename `screenToFlowCoordinate` to `screenToFlowPosition` +- ⚠️ rename `flowToScreenCoordinate` to `flowToScreenPosition` +- ⚠️ rename `getTransformForBounds` to `getViewportForBounds` (return `{ x: number, y: number, zoom: number }` instead of `[number, number, number]`) +- ⚠️ rename `getRectOfNodes` to `getNodesBounds` +- simplify handle default styles, so that it's easier to override them +- added e2e tests + +## 0.0.26 + +- fixes broken version on npm + ## 0.0.25 - add `toObject` to `useSvelteFlow` hook diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 810d9c45..3beae0b8 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "0.0.26", + "version": "0.0.27", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", diff --git a/packages/svelte/src/lib/components/BaseEdge/BaseEdge.svelte b/packages/svelte/src/lib/components/BaseEdge/BaseEdge.svelte index e53bd7c1..4e5b39d1 100644 --- a/packages/svelte/src/lib/components/BaseEdge/BaseEdge.svelte +++ b/packages/svelte/src/lib/components/BaseEdge/BaseEdge.svelte @@ -24,7 +24,6 @@ - import { getRectOfNodes } from '@xyflow/system'; + import { getNodesBounds } from '@xyflow/system'; import { useStore } from '$lib/store'; import { Selection } from '$lib/components/Selection'; @@ -9,13 +9,13 @@ const { selectionRectMode, nodes } = store; $: selectedNodes = $nodes.filter((n) => n.selected); - $: rect = getRectOfNodes(selectedNodes); + $: bounds = getNodesBounds(selectedNodes); {#if selectedNodes && $selectionRectMode === 'nodes'}
diff --git a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte index e02a0f98..e422b444 100644 --- a/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte +++ b/packages/svelte/src/lib/components/NodeWrapper/NodeWrapper.svelte @@ -1,13 +1,7 @@
{ deletedNodes: Node[]; deletedEdges: Edge[] }; - screenToFlowCoordinate: (position: XYPosition) => XYPosition; - flowToScreenCoordinate: (position: XYPosition) => XYPosition; + screenToFlowPosition: (position: XYPosition) => XYPosition; + flowToScreenPosition: (position: XYPosition) => XYPosition; viewport: Writable; getConnectedEdges: (id: string | (Node | { id: Node['id'] })[]) => Edge[]; getIncomers: (node: string | Node | { id: Node['id'] }) => Node[]; @@ -118,7 +118,7 @@ export function useSvelteFlow(): { }, fitView, fitBounds: (bounds: Rect, options?: FitBoundsOptions) => { - const [x, y, zoom] = getTransformForBounds( + const viewport = getViewportForBounds( bounds, get(width), get(height), @@ -127,14 +127,7 @@ export function useSvelteFlow(): { options?.padding ?? 0.1 ); - get(panZoom)?.setViewport( - { - x, - y, - zoom - }, - { duration: options?.duration } - ); + get(panZoom)?.setViewport(viewport, { duration: options?.duration }); }, getIntersectingNodes: ( nodeOrRect: Node | { id: Node['id'] } | Rect, @@ -201,45 +194,43 @@ export function useSvelteFlow(): { deletedEdges: matchingEdges }; }, - screenToFlowCoordinate: (position: XYPosition) => { + screenToFlowPosition: (position: XYPosition) => { const _domNode = get(domNode); - if (_domNode) { - const _snapGrid = get(snapGrid); - const { x, y, zoom } = get(viewport); - const { x: domX, y: domY } = _domNode.getBoundingClientRect(); - - const correctedPosition = { - x: position.x - domX, - y: position.y - domY - }; - - return pointToRendererPoint( - correctedPosition, - [x, y, zoom], - _snapGrid !== null, - _snapGrid || [1, 1] - ); + if (!_domNode) { + return position; } - return { x: 0, y: 0 }; + const _snapGrid = get(snapGrid); + const { x, y, zoom } = get(viewport); + const { x: domX, y: domY } = _domNode.getBoundingClientRect(); + const correctedPosition = { + x: position.x - domX, + y: position.y - domY + }; + + return pointToRendererPoint( + correctedPosition, + [x, y, zoom], + _snapGrid !== null, + _snapGrid || [1, 1] + ); }, - flowToScreenCoordinate: (position: XYPosition) => { + flowToScreenPosition: (position: XYPosition) => { const _domNode = get(domNode); - if (_domNode) { - const { x, y, zoom } = get(viewport); - const { x: domX, y: domY } = _domNode.getBoundingClientRect(); - - const rendererPosition = rendererPointToPoint(position, [x, y, zoom]); - - return { - x: rendererPosition.x + domX, - y: rendererPosition.y + domY - }; + if (!_domNode) { + return position; } - return { x: 0, y: 0 }; + const { x, y, zoom } = get(viewport); + const { x: domX, y: domY } = _domNode.getBoundingClientRect(); + const rendererPosition = rendererPointToPoint(position, [x, y, zoom]); + + return { + x: rendererPosition.x + domX, + y: rendererPosition.y + domY + }; }, getConnectedEdges: (node) => { const nodeIds = new Set(); diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 13666896..b8620d79 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -87,6 +87,6 @@ export { getSmoothStepPath, type GetStraightPathParams, getStraightPath, - getTransformForBounds, - getRectOfNodes + getViewportForBounds, + getNodesBounds } from '@xyflow/system'; diff --git a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte index 95c8c7d3..f3fba426 100644 --- a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte +++ b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte @@ -7,7 +7,7 @@