Merge branch 'main' into patch-1

This commit is contained in:
Moritz Klack
2025-05-20 09:48:55 +02:00
committed by GitHub
345 changed files with 13259 additions and 8242 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@xyflow/svelte': patch
---
Fix setting nodesInitialized multiple times
+7
View File
@@ -0,0 +1,7 @@
---
'@xyflow/react': minor
'@xyflow/svelte': minor
'@xyflow/system': minor
---
Add an `ease` and `interpolate` option to all function that alter the viewport
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
name: 'Playwright Tests' name: 'Playwright Tests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.45.0-jammy image: mcr.microsoft.com/playwright:v1.51.1-jammy
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v4
+1 -1
View File
@@ -25,5 +25,5 @@ jobs:
title: Release packages title: Release packages
commit: 'chore(packages): bump' commit: 'chore(packages): bump'
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+5 -5
View File
@@ -20,19 +20,19 @@ That being said, our libraries are only interesting because of the people who ma
The things we need the most help for the library and its community are: The things we need the most help for the library and its community are:
**🐛 Bug reports:** We simply cant catch them all. Check [existing issues](https://github.com/wbkd/react-flow/issues/new/choose) and discussion first, then [create a new issue](https://github.com/wbkd/react-flow/issues/new/choose) to tell us whats up. **🐛 Bug reports:** We simply cant 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 whats up.
**💬 Answering questions** in our [Discord Server](https://discord.gg/Bqt6xrs) and [Github discussions](https://github.com/wbkd/react-flow/discussions). **💬 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 well happily share them! 🎬 **Create tutorials**. Send them to us and well happily share them!
**✏️ Edit our [Docs](https://reactflow.dev/docs/introduction/)**: Make changes in the [react-flow-docs repo](https://github.com/wbkd/react-flow-docs), or click the "edit this page” button that lives on every doc site. **✏️ 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/wbkd/react-flow/blob/main/CODE_OF_CONDUCT.md). All interactions should be done with care following our [Code of Conduct](https://github.com/xyflow/xyflow/blob/main/CODE_OF_CONDUCT.md).
## Enhancements ## 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/wbkd/react-flow/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/wbkd/react-flow/discussions/categories/new-features), and in the end they are decided by the core team. 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 youre proud of, just to learn that weve already been working on the same thing, or that we feel doesnt fit into the core library. 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 youre proud of, just to learn that weve already been working on the same thing, or that we feel doesnt fit into the core library.
+1 -1
View File
@@ -165,7 +165,7 @@ React Flow and Svelte Flow are maintained by the team behind [xyflow](https://xy
- Christopher • [Twitter](https://twitter.com/chrtze) • [Github](https://github.com/chrtze) - Christopher • [Twitter](https://twitter.com/chrtze) • [Github](https://github.com/chrtze)
- Hayleigh • [Twitter](https://twitter.com/hayleighdotdev) • [Github](https://github.com/hayleigh-dot-dev) - Hayleigh • [Twitter](https://twitter.com/hayleighdotdev) • [Github](https://github.com/hayleigh-dot-dev)
- John • [Website](https://johnrobbdesign.com/) • [Mastodon](https://mastodon.social/@johnrobbjr) - Abbey • [Github](https://github.com/printerscanner)
- Moritz • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick) - Moritz • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick)
- Peter • [Github](https://github.com/peterkogo) - Peter • [Github](https://github.com/peterkogo)
+1
View File
@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" /> /// <reference types="astro/client" />
+2 -1
View File
@@ -7,6 +7,7 @@
"dev": "vite --port 3000 --open --host", "dev": "vite --port 3000 --open --host",
"serve": "vite serve --port 3000", "serve": "vite serve --port 3000",
"build": "vite build", "build": "vite build",
"preview": "vite preview",
"test:dev": "cypress open", "test:dev": "cypress open",
"test": "pnpm test-component && pnpm test-e2e", "test": "pnpm test-component && pnpm test-e2e",
"test-component": "cypress run --component", "test-component": "cypress run --component",
@@ -37,7 +38,7 @@
"cypress": "13.6.6", "cypress": "13.6.6",
"cypress-real-events": "1.12.0", "cypress-real-events": "1.12.0",
"start-server-and-test": "^2.0.2", "start-server-and-test": "^2.0.2",
"typescript": "5.2.2", "typescript": "5.4.5",
"vite": "4.5.0" "vite": "4.5.0"
} }
} }
+10 -4
View File
@@ -1,5 +1,6 @@
import Basic from '../examples/Basic'; import Basic from '../examples/Basic';
import Backgrounds from '../examples/Backgrounds'; import Backgrounds from '../examples/Backgrounds';
import BrokenNodes from '../examples/BrokenNodes';
import ColorMode from '../examples/ColorMode'; import ColorMode from '../examples/ColorMode';
import ClickDistance from '../examples/ClickDistance'; import ClickDistance from '../examples/ClickDistance';
import ControlledUncontrolled from '../examples/ControlledUncontrolled'; import ControlledUncontrolled from '../examples/ControlledUncontrolled';
@@ -49,7 +50,7 @@ import NodeToolbar from '../examples/NodeToolbar';
import UseConnection from '../examples/UseConnection'; import UseConnection from '../examples/UseConnection';
import UseNodesInitialized from '../examples/UseNodesInit'; import UseNodesInitialized from '../examples/UseNodesInit';
import UseNodesData from '../examples/UseNodesData'; import UseNodesData from '../examples/UseNodesData';
import UseHandleConnections from '../examples/UseHandleConnections'; import UseNodeConnections from '../examples/UseNodeConnections';
import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop'; import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop';
import DevTools from '../examples/DevTools'; import DevTools from '../examples/DevTools';
import Redux from '../examples/Redux'; import Redux from '../examples/Redux';
@@ -77,6 +78,11 @@ const routes: IRoute[] = [
path: 'backgrounds', path: 'backgrounds',
component: Backgrounds, component: Backgrounds,
}, },
{
name: 'Broken Nodes',
path: 'broken-nodes',
component: BrokenNodes,
},
{ {
name: 'Color Mode', name: 'Color Mode',
path: 'color-mode', path: 'color-mode',
@@ -313,9 +319,9 @@ const routes: IRoute[] = [
component: UseReactFlow, component: UseReactFlow,
}, },
{ {
name: 'useHandleConnections', name: 'useNodeConnections',
path: 'usehandleconnections', path: 'usenodeconnections',
component: UseHandleConnections, component: UseNodeConnections,
}, },
{ {
name: 'useNodesData', name: 'useNodesData',
@@ -47,7 +47,7 @@ const AddNodeOnEdgeDrop = () => {
(event) => { (event) => {
if (!connectingNodeId.current) return; if (!connectingNodeId.current) return;
const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('react-flow__pane'); const targetIsPane = (event.target as Partial<Element> | null)?.classList?.contains('react-flow__pane');
if (targetIsPane && 'clientX' in event && 'clientY' in event) { if (targetIsPane && 'clientX' in event && 'clientY' in event) {
// we need to remove the wrapper bounds, in order to get the correct position // we need to remove the wrapper bounds, in order to get the correct position
+20 -2
View File
@@ -11,6 +11,7 @@ import {
useReactFlow, useReactFlow,
Panel, Panel,
OnNodeDrag, OnNodeDrag,
FitViewOptions,
} from '@xyflow/react'; } from '@xyflow/react';
const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes); const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
@@ -54,10 +55,23 @@ const initialEdges: Edge[] = [
]; ];
const defaultEdgeOptions = {}; const defaultEdgeOptions = {};
const fitViewOptions: FitViewOptions = {
padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 },
};
const BasicFlow = () => { const BasicFlow = () => {
const { addNodes, setNodes, getNodes, setEdges, getEdges, deleteElements, updateNodeData, toObject, setViewport } = const {
useReactFlow(); addNodes,
setNodes,
getNodes,
setEdges,
getEdges,
deleteElements,
updateNodeData,
toObject,
setViewport,
fitView,
} = useReactFlow();
const updatePos = () => { const updatePos = () => {
setNodes((nodes) => setNodes((nodes) =>
@@ -104,6 +118,7 @@ const BasicFlow = () => {
]); ]);
setEdges([{ id: 'a-b', source: 'a', target: 'b' }]); setEdges([{ id: 'a-b', source: 'a', target: 'b' }]);
fitView();
}; };
const onUpdateNode = () => { const onUpdateNode = () => {
@@ -117,12 +132,14 @@ const BasicFlow = () => {
position: { x: Math.random() * 300, y: Math.random() * 300 }, position: { x: Math.random() * 300, y: Math.random() * 300 },
className: 'light', className: 'light',
}); });
fitView();
}; };
return ( return (
<ReactFlow <ReactFlow
defaultNodes={initialNodes} defaultNodes={initialNodes}
defaultEdges={initialEdges} defaultEdges={initialEdges}
onNodesChange={console.log}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
@@ -134,6 +151,7 @@ const BasicFlow = () => {
minZoom={0.2} minZoom={0.2}
maxZoom={4} maxZoom={4}
fitView fitView
fitViewOptions={fitViewOptions}
defaultEdgeOptions={defaultEdgeOptions} defaultEdgeOptions={defaultEdgeOptions}
selectNodesOnDrag={false} selectNodesOnDrag={false}
elevateEdgesOnSelect elevateEdgesOnSelect
@@ -0,0 +1,80 @@
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;
@@ -0,0 +1,14 @@
@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;
}
@@ -0,0 +1,38 @@
import { FC } from 'react';
import { BaseEdge, EdgeProps, EdgeText, getSmoothStepPath } from '@xyflow/react';
import './CustomEdge3.css';
const CustomEdge: FC<EdgeProps> = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
data,
}) => {
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
});
return (
<>
<BaseEdge id={id} path={edgePath} pathLength={100} />
<EdgeText
x={labelX}
y={labelY - 5}
label={data.text}
labelBgStyle={{ fill: 'transparent' }}
onClick={() => console.log(data)}
/>
</>
);
};
export default CustomEdge;
@@ -16,6 +16,7 @@ import {
import CustomEdge from './CustomEdge'; import CustomEdge from './CustomEdge';
import CustomEdge2 from './CustomEdge2'; import CustomEdge2 from './CustomEdge2';
import CustomEdge3 from './CustomEdge3';
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
@@ -63,6 +64,12 @@ const initialNodes: Node[] = [
data: { label: 'Output 9' }, data: { label: 'Output 9' },
position: { x: 675, y: 500 }, position: { x: 675, y: 500 },
}, },
{
id: '10',
type: 'output',
data: { label: 'Output 10' },
position: { x: 50, y: 400 },
},
]; ];
const initialEdges: Edge[] = [ const initialEdges: Edge[] = [
@@ -137,6 +144,13 @@ const initialEdges: Edge[] = [
type: 'custom2', type: 'custom2',
data: { text: 'custom edge 2' }, data: { text: 'custom edge 2' },
}, },
{
id: 'e3a-10',
source: '3a',
target: '10',
type: 'custom3',
data: { text: 'custom edge 3' },
},
{ {
id: 'e5-6', id: 'e5-6',
source: '5', source: '5',
@@ -173,6 +187,7 @@ const initialEdges: Edge[] = [
const edgeTypes: EdgeTypes = { const edgeTypes: EdgeTypes = {
custom: CustomEdge, custom: CustomEdge,
custom2: CustomEdge2, custom2: CustomEdge2,
custom3: CustomEdge3,
}; };
const defaultEdgeOptions = { const defaultEdgeOptions = {
@@ -0,0 +1,21 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps, NodeResizeControl, ResizeControlVariant } from '@xyflow/react';
const CustomResizerNode: FC<NodeProps> = ({ data }) => {
return (
<>
<NodeResizeControl
variant={ResizeControlVariant.Handle}
position="bottom-right"
resizeDirection="horizontal"
minWidth={100}
maxWidth={500}
/>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomResizerNode);
@@ -15,12 +15,14 @@ import DefaultResizer from './DefaultResizer';
import CustomResizer from './CustomResizer'; import CustomResizer from './CustomResizer';
import VerticalResizer from './VerticalResizer'; import VerticalResizer from './VerticalResizer';
import HorizontalResizer from './HorizontalResizer'; import HorizontalResizer from './HorizontalResizer';
import BottomRightResizer from './BottomRightResizer';
const nodeTypes = { const nodeTypes = {
defaultResizer: DefaultResizer, defaultResizer: DefaultResizer,
customResizer: CustomResizer, customResizer: CustomResizer,
verticalResizer: VerticalResizer, verticalResizer: VerticalResizer,
horizontalResizer: HorizontalResizer, horizontalResizer: HorizontalResizer,
bottomRightResizer: BottomRightResizer,
}; };
const nodeStyle = { const nodeStyle = {
@@ -166,6 +168,13 @@ const initialNodes: Node[] = [
expandParent: true, expandParent: true,
style: { ...nodeStyle }, style: { ...nodeStyle },
}, },
{
id: '6',
type: 'bottomRightResizer',
data: { label: 'Bottom Right with horizontal direction' },
position: { x: 500, y: 500 },
style: { ...nodeStyle },
},
]; ];
const CustomNodeFlow = () => { const CustomNodeFlow = () => {
+13 -3
View File
@@ -12,6 +12,8 @@ import {
Controls, Controls,
Background, Background,
Panel, Panel,
ReactFlowProvider,
useReactFlow,
} from '@xyflow/react'; } from '@xyflow/react';
import { getNodesAndEdges } from './utils'; import { getNodesAndEdges } from './utils';
@@ -22,6 +24,7 @@ const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(25, 25);
const StressFlow = () => { const StressFlow = () => {
const [nodes, setNodes] = useState<Node[]>(initialNodes); const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges); const [edges, setEdges] = useState<Edge[]>(initialEdges);
const { fitView } = useReactFlow();
const onConnect = useCallback((connection: Connection) => { const onConnect = useCallback((connection: Connection) => {
setEdges((eds) => addEdge(connection, eds)); setEdges((eds) => addEdge(connection, eds));
}, []); }, []);
@@ -191,12 +194,13 @@ const StressFlow = () => {
return { return {
...n, ...n,
position: { position: {
x: Math.random() * window.innerWidth, x: Math.random() * window.innerWidth * 4,
y: Math.random() * window.innerHeight, y: Math.random() * window.innerHeight * 4,
}, },
}; };
}); });
}); });
fitView();
}; };
const updateElements = () => { const updateElements = () => {
@@ -240,4 +244,10 @@ const StressFlow = () => {
); );
}; };
export default StressFlow; export default function StressFlowProvider() {
return (
<ReactFlowProvider>
<StressFlow />
</ReactFlowProvider>
);
}
@@ -1,5 +1,5 @@
import { memo, FC, useEffect, useCallback } from 'react'; import { memo, FC, useEffect, useCallback } from 'react';
import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleProps } from '@xyflow/react'; import { Handle, Position, NodeProps, useNodeConnections, Connection, HandleProps } from '@xyflow/react';
function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) { function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) {
const onConnect = useCallback( const onConnect = useCallback(
@@ -11,9 +11,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
(connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections), (connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections),
[nodeId] [nodeId]
); );
const connections = useHandleConnections({ const connections = useNodeConnections({
type: handleProps.type, handleType: handleProps.type,
id: handleProps.id, handleId: handleProps.id,
onConnect, onConnect,
onDisconnect, onDisconnect,
}); });
@@ -28,10 +28,11 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
const CustomNode: FC<NodeProps> = ({ id }) => { const CustomNode: FC<NodeProps> = ({ id }) => {
return ( return (
<div style={{ background: '#333', color: '#fff', padding: 10, fontSize: 12, borderRadius: 10 }}> <div style={{ background: '#333', color: '#fff', padding: 10, fontSize: 12, borderRadius: 10 }}>
<CustomHandle nodeId={id} type="target" position={Position.Left} /> <CustomHandle nodeId={id} type="target" position={Position.Left} id="t1" style={{ top: 10 }} />
<CustomHandle nodeId={id} type="target" position={Position.Left} id="t2" style={{ top: 20 }} />
<div>node {id}</div> <div>node {id}</div>
<CustomHandle nodeId={id} type="source" position={Position.Right} id="a" style={{ top: 10 }} /> <CustomHandle nodeId={id} type="source" position={Position.Right} id="s1" style={{ top: 10 }} />
<CustomHandle nodeId={id} type="source" position={Position.Right} id="b" style={{ top: 20 }} /> <CustomHandle nodeId={id} type="source" position={Position.Right} id="s2" style={{ top: 20 }} />
</div> </div>
); );
}; };
@@ -1,5 +1,5 @@
import { memo, FC, useEffect, useCallback } from 'react'; import { memo, FC, useEffect, useCallback } from 'react';
import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleProps } from '@xyflow/react'; import { Handle, Position, NodeProps, useNodeConnections, Connection, HandleProps } from '@xyflow/react';
function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) { function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) {
const onConnect = useCallback( const onConnect = useCallback(
@@ -14,9 +14,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
}, },
[nodeId] [nodeId]
); );
const connections = useHandleConnections({ const connections = useNodeConnections({
type: handleProps.type, handleType: handleProps.type,
id: handleProps.id, handleId: handleProps.id,
onConnect, onConnect,
onDisconnect, onDisconnect,
}); });
@@ -69,17 +69,16 @@ const initEdges = [
source: '1', source: '1',
target: '3', target: '3',
}, },
{ {
id: 'e4a-5', id: 'e4a-5',
source: '4', source: '4',
sourceHandle: 'a', sourceHandle: 's1',
target: '5', target: '5',
}, },
{ {
id: 'e4b-5', id: 'e4b-5',
source: '4', source: '4',
sourceHandle: 'b', sourceHandle: 's2',
target: '6', target: '6',
}, },
]; ];
@@ -92,6 +91,8 @@ const CustomNodeFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
console.log(edges);
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]); const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
return ( return (
@@ -1,10 +1,10 @@
import { memo } from 'react'; import { memo } from 'react';
import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react'; import { Handle, Position, useNodeConnections, useNodesData } from '@xyflow/react';
import { isTextNode, type MyNode } from '.'; import { isTextNode, type MyNode } from '.';
function ResultNode() { function ResultNode() {
const connections = useHandleConnections({ const connections = useNodeConnections({
type: 'target', handleType: 'target',
}); });
const nodesData = useNodesData<MyNode>(connections.map((connection) => connection.source)); const nodesData = useNodesData<MyNode>(connections.map((connection) => connection.source));
const textNodes = nodesData.filter(isTextNode); const textNodes = nodesData.filter(isTextNode);
@@ -1,11 +1,11 @@
import { memo, useEffect } from 'react'; import { memo, useEffect } from 'react';
import { Position, NodeProps, useReactFlow, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; import { Position, NodeProps, useReactFlow, Handle, useNodeConnections, useNodesData } from '@xyflow/react';
import { isTextNode, type TextNode, type MyNode } from '.'; import { isTextNode, type TextNode, type MyNode } from '.';
function UppercaseNode({ id }: NodeProps) { function UppercaseNode({ id }: NodeProps) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const connections = useHandleConnections({ const connections = useNodeConnections({
type: 'target', handleType: 'target',
}); });
const nodesData = useNodesData<MyNode>(connections[0]?.source); const nodesData = useNodesData<MyNode>(connections[0]?.source);
const textNode = isTextNode(nodesData) ? nodesData : null; const textNode = isTextNode(nodesData) ? nodesData : null;
@@ -21,7 +21,7 @@ export type UppercaseNode = Node<{ text: string }, 'uppercase'>;
export type MyNode = TextNode | ResultNode | UppercaseNode; export type MyNode = TextNode | ResultNode | UppercaseNode;
export function isTextNode(node: any): node is TextNode | UppercaseNode { export function isTextNode(node: any): node is TextNode | UppercaseNode {
return node.type === 'text' || node.type === 'uppercase'; return !node || !node.type ? false : node.type === 'text' || node.type === 'uppercase';
} }
const nodeTypes = { const nodeTypes = {
@@ -10,7 +10,7 @@ import {
useEdgesState, useEdgesState,
useOnSelectionChange, useOnSelectionChange,
OnSelectionChangeParams, OnSelectionChangeParams,
OnSelectionChangeFunc, Panel,
} from '@xyflow/react'; } from '@xyflow/react';
const initialNodes: Node[] = [ const initialNodes: Node[] = [
@@ -52,6 +52,12 @@ const Flow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]);
const [elementsSelectable, setElementsSelectable] = useState<boolean>(true);
const [secondLoggerActive, setSecondLoggerActive] = useState<boolean>(true);
const toggleSecondLogger = () => {
setSecondLoggerActive(!secondLoggerActive);
};
return ( return (
<ReactFlow <ReactFlow
@@ -60,27 +66,17 @@ const Flow = () => {
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}
/> elementsSelectable={elementsSelectable}
); >
}; <Panel>
<button onClick={toggleSecondLogger}>{secondLoggerActive ? 'Disable' : 'Enable'} Logger 2</button>
<button onClick={() => setElementsSelectable((s) => !s)}>toggle selectable</button>
</Panel>
const WrappedFlow = () => {
const [secondLoggerActive, setSecondLoggerActive] = useState<boolean>(true);
const toggleSecondLogger = () => {
setSecondLoggerActive(!secondLoggerActive);
};
return (
<ReactFlowProvider>
<Flow />
<SelectionLogger id="Logger 1" /> <SelectionLogger id="Logger 1" />
{secondLoggerActive && <SelectionLogger id="Logger 2" />} {secondLoggerActive && <SelectionLogger id="Logger 2" />}
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}> </ReactFlow>
<button onClick={toggleSecondLogger}>{secondLoggerActive ? 'Disable' : 'Enable'} Logger 2</button>
</div>
</ReactFlowProvider>
); );
}; };
export default WrappedFlow; export default Flow;
@@ -170,7 +170,6 @@ const UseZoomPanHelperFlow = () => {
onConnect={onConnect} onConnect={onConnect}
onPaneClick={onPaneClick} onPaneClick={onPaneClick}
fitView fitView
fitViewOptions={{ duration: 1200, padding: 0.2 }}
maxZoom={Infinity} maxZoom={Infinity}
> >
<Panel position="top-right"> <Panel position="top-right">
@@ -183,7 +182,16 @@ const UseZoomPanHelperFlow = () => {
console.log('fit view success'); console.log('fit view success');
}} }}
> >
fitView fitView default
</button>
<button
onClick={async () => {
console.log('fit view start');
await fitView({ duration: 1200, padding: 0.3, ease: (t) => +t });
console.log('fit view success');
}}
>
fitView linear
</button> </button>
<button onClick={onAddNode}>add node</button> <button onClick={onAddNode}>add node</button>
<button onClick={onResetNodes}>reset nodes</button> <button onClick={onResetNodes}>reset nodes</button>
@@ -4,6 +4,7 @@ export default {
flowProps: { flowProps: {
fitView: true, fitView: true,
multiSelectionKeyCode: 's', multiSelectionKeyCode: 's',
deleteKeyCode: 'd',
nodes: [ nodes: [
{ {
id: '1', id: '1',
@@ -3,6 +3,8 @@ import DragHandleNode from './components/DragHandleNode';
export default { export default {
flowProps: { flowProps: {
fitView: true, fitView: true,
deleteKeyCode: 'd',
multiSelectionKeyCode: 's',
nodeTypes: { nodeTypes: {
DragHandleNode, DragHandleNode,
}, },
+16 -16
View File
@@ -12,25 +12,25 @@
"format": "prettier --plugin-search-dir . --write ." "format": "prettier --plugin-search-dir . --write ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.2.1", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.5.10", "@sveltejs/kit": "^2.20.7",
"@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/eslint-plugin": "^8.30.1",
"@typescript-eslint/parser": "^6.10.0", "@typescript-eslint/parser": "^8.30.1",
"eslint": "^8.53.0", "eslint": "^9.24.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^10.1.2",
"eslint-plugin-svelte": "^2.35.0", "eslint-plugin-svelte": "^3.5.1",
"prettier": "^3.0.3", "prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.0.3", "prettier-plugin-svelte": "^3.3.3",
"svelte": "^4.2.12", "svelte": "^5.27.0",
"svelte-check": "^3.6.6", "svelte-check": "^4.1.6",
"tslib": "^2.6.2", "tslib": "^2.8.1",
"typescript": "^5.2.2", "typescript": "^5.8.3",
"vite": "^5.2.12" "vite": "^6.3.0"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@dagrejs/dagre": "^1.0.4", "@dagrejs/dagre": "^1.1.4",
"@sveltejs/vite-plugin-svelte": "^3.1.1", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@xyflow/svelte": "workspace:^" "@xyflow/svelte": "workspace:^"
} }
} }
@@ -35,7 +35,7 @@
<header> <header>
<div class="logo">Svelte Flow</div> <div class="logo">Svelte Flow</div>
<select on:change={onChange} value={$page.route.id}> <select onchange={onChange} value={$page.route.id}>
{#each routes as route} {#each routes as route}
<option value={`/examples/${route}`}>{route}</option> <option value={`/examples/${route}`}>{route}</option>
{/each} {/each}
@@ -3,6 +3,8 @@ import { MarkerType } from '@xyflow/svelte';
export default { export default {
flowProps: { flowProps: {
fitView: true, fitView: true,
multiSelectionKey: ['Meta', 's'],
deleteKey: 'd',
nodes: [ nodes: [
{ {
id: '1', id: '1',
@@ -1,9 +1,18 @@
<script lang="ts"> <script lang="ts">
import { NodeToolbar, type NodeProps, Handle, Position } from '@xyflow/svelte'; import {
NodeToolbar,
type NodeProps,
type Node,
Handle,
Position,
type Align
} from '@xyflow/svelte';
type $$Props = NodeProps; let {
data
export let data: $$Props['data']; }: NodeProps<
Node<{ label: string; toolbarPosition: Position; toolbarAlign: Align; toolbarVisible: boolean }>
> = $props();
</script> </script>
<NodeToolbar <NodeToolbar
@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { NodeProps } from '@xyflow/svelte'; import type { NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps; let {}: NodeProps = $props();
</script> </script>
<div class="container"> <div class="container">
<div class="drag-handle custom-drag-handle" /> <div class="drag-handle custom-drag-handle"></div>
</div> </div>
<style> <style>
@@ -4,6 +4,9 @@ export default {
flowProps: { flowProps: {
fitView: true, fitView: true,
nodeDragThreshold: 0, nodeDragThreshold: 0,
autoPanOnConnect: false,
autoPanOnNodeDrag: false,
deleteKey: 'd',
nodeTypes: { nodeTypes: {
DragHandleNode DragHandleNode
}, },
+2
View File
@@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = false;
@@ -1,11 +1,16 @@
<script lang="ts"> <script lang="ts">
import { Header } from '$components/Header'; import { Header } from '$components/Header';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script> </script>
<div class="app"> <div class="app">
<Header /> <Header />
<div class="flow"> <div class="flow">
<slot /> {@render children?.()}
</div> </div>
</div> </div>
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
useNodeConnections,
useSvelteFlow, useSvelteFlow,
type Edge, type Edge,
type Node, type Node,
@@ -19,21 +19,27 @@
} }
]; ];
const nodes = writable<Node[]>(initialNodes); let nodes = $state.raw<Node[]>(initialNodes);
const edges = writable<Edge[]>([]); let edges = $state.raw<Edge[]>([]);
let connectingNodeId: string | null = '0'; let connectingNodeId: string | null = $state('0');
let rect: DOMRectReadOnly; let rect = $state<DOMRectReadOnly>();
let id = 1; let id = 1;
const getId = () => `${id++}`; const getId = () => `${id++}`;
const { screenToFlowPosition, flowToScreenPosition } = useSvelteFlow(); const { screenToFlowPosition, flowToScreenPosition } = useSvelteFlow();
const connections = useNodeConnections({ id: '0', handleType: 'source' });
$inspect(connections.current);
const handleConnectEnd: OnConnectEnd = (event) => { const handleConnectEnd: OnConnectEnd = (event) => {
if (!connectingNodeId) return; if (!connectingNodeId) return;
// See of connection landed inside the flow pane // See of connection landed inside the flow pane
const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('svelte-flow__pane'); const targetIsPane = (event.target as Partial<Element> | null)?.classList?.contains(
'svelte-flow__pane'
);
if (targetIsPane && 'clientX' in event && 'clientY' in event) { if (targetIsPane && 'clientX' in event && 'clientY' in event) {
const id = getId(); const id = getId();
const position = { const position = {
@@ -56,15 +62,14 @@
origin: [0.5, 0.0] origin: [0.5, 0.0]
}; };
$nodes.push(newNode); nodes = [...nodes, newNode];
$edges.push({
const newEdge = {
source: connectingNodeId, source: connectingNodeId,
target: id, target: id,
id: `${connectingNodeId}--${id}` id: `${connectingNodeId}--${id}`
}); };
edges = [...edges, newEdge];
$nodes = $nodes;
$edges = $edges;
} }
}; };
</script> </script>
@@ -73,8 +78,8 @@
<div class="wrapper" bind:contentRect={rect}> <div class="wrapper" bind:contentRect={rect}>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
fitView fitView
fitViewOptions={{ padding: 2 }} fitViewOptions={{ padding: 2 }}
onconnectstart={(_, { nodeId }) => { onconnectstart={(_, { nodeId }) => {
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -18,7 +17,7 @@
targetPosition: Position.Left targetPosition: Position.Left
}; };
const nodes = writable([ let nodes = $state.raw([
{ {
id: 'A', id: 'A',
position: { x: 0, y: 150 }, position: { x: 0, y: 150 },
@@ -30,16 +29,16 @@
{ id: 'D', position: { x: 250, y: 300 }, data: { label: 'D' }, ...nodeDefaults } { id: 'D', position: { x: 250, y: 300 }, data: { label: 'D' }, ...nodeDefaults }
]); ]);
const edges = writable([ let edges = $state.raw([
{ id: 'A-B', source: 'A', target: 'B' }, { id: 'A-B', source: 'A', target: 'B' },
{ id: 'A-C', source: 'A', target: 'C' }, { id: 'A-C', source: 'A', target: 'C' },
{ id: 'A-D', source: 'A', target: 'D' } { id: 'A-D', source: 'A', target: 'D' }
]); ]);
let colorMode: ColorMode = 'light'; let colorMode: ColorMode = $state('light');
</script> </script>
<SvelteFlow {nodes} {edges} {colorMode} fitView> <SvelteFlow bind:nodes bind:edges {colorMode} colorModeSSR={'light'} fitView>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { SvelteFlow } from '@xyflow/svelte'; import { SvelteFlow } from '@xyflow/svelte';
import { Background, BackgroundVariant, type Edge, type Node } from '@xyflow/svelte'; import { Background, BackgroundVariant, type Edge, type Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
import CustomNode from './CustomNode.svelte'; import CustomNode from './CustomNode.svelte';
import ConnectionLine from './ConnectionLine.svelte'; import ConnectionLine from './ConnectionLine.svelte';
@@ -12,7 +11,7 @@
custom: CustomNode custom: CustomNode
}; };
const nodes = writable<Node[]>([ let nodes = $state.raw<Node[]>([
{ {
id: 'connectionline-1', id: 'connectionline-1',
type: 'custom', type: 'custom',
@@ -21,12 +20,11 @@
} }
]); ]);
const edges = writable<Edge[]>([]); let edges = $state.raw<Edge[]>([]);
</script> </script>
<div style="height:100vh;"> <div style="height:100vh;">
<SvelteFlow {nodeTypes} {nodes} {edges} fitView> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView connectionLineComponent={ConnectionLine}>
<ConnectionLine slot="connectionLine" />
<Background variant={BackgroundVariant.Lines} /> <Background variant={BackgroundVariant.Lines} />
</SvelteFlow> </SvelteFlow>
</div> </div>
@@ -3,22 +3,24 @@
const connection = useConnection(); const connection = useConnection();
let path: string | null = null; let path: string | null = $derived.by(() => {
if (connection.current.inProgress) {
$: if ($connection.inProgress) { const { from, to, fromPosition, toPosition } = connection.current;
const { from, to, fromPosition, toPosition } = $connection; const pathParams = {
const pathParams = { sourceX: from.x,
sourceX: from.x, sourceY: from.y,
sourceY: from.y, sourcePosition: fromPosition,
sourcePosition: fromPosition, targetX: to.x,
targetX: to.x, targetY: to.y,
targetY: to.y, targetPosition: toPosition
targetPosition: toPosition };
}; const [path] = getBezierPath(pathParams);
[path] = getBezierPath(pathParams); return path;
} }
return null;
});
</script> </script>
{#if $connection.inProgress} {#if connection.current.inProgress}
<path d={path} fill="none" stroke={$connection.fromHandle.id} /> <path d={path} fill="none" stroke={connection.current.fromHandle.id} />
{/if} {/if}
@@ -1,10 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps; let { isConnectable }: NodeProps = $props();
export let isConnectable: $$Props['isConnectable'];
$$restProps;
const handleStyle = 'width: 10px; height: 10px; bottom: -5px;'; const handleStyle = 'width: 10px; height: 10px; bottom: -5px;';
</script> </script>
@@ -1,5 +1,13 @@
<script lang="ts" module>
// TODO: Is this the best way?
class background {
color: string = $state('#1A192B');
}
export const bg = new background();
</script>
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -20,9 +28,7 @@
colorNode: CustomNode colorNode: CustomNode
}; };
const bgColor = writable('#1A192B'); let nodes = $state.raw<Node[]>([
const nodes = writable<Node[]>([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -33,7 +39,7 @@
{ {
id: '2', id: '2',
type: 'colorNode', type: 'colorNode',
data: { colorStore: bgColor }, data: {},
position: { x: 250, y: 50 } position: { x: 250, y: 50 }
}, },
{ {
@@ -52,7 +58,7 @@
} }
]); ]);
const edges = writable<Edge[]>([ let edges = $state.raw<Edge[]>([
{ {
id: 'e1-2', id: 'e1-2',
source: '1', source: '1',
@@ -81,10 +87,10 @@
</script> </script>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
{nodeTypes} {nodeTypes}
style="--xy-background-color: {$bgColor}" style="--xy-background-color: {bg.color}"
fitView fitView
onconnect={onConnect} onconnect={onConnect}
> >
@@ -1,12 +1,8 @@
<script lang="ts"> <script lang="ts">
import type { Writable } from 'svelte/store';
import { Handle, Position, type NodeProps, type Node } from '@xyflow/svelte'; import { Handle, Position, type NodeProps, type Node } from '@xyflow/svelte';
import { bg } from './+page.svelte';
type $$Props = NodeProps<Node<{ colorStore: Writable<string> }>>; let { data }: NodeProps<Node> = $props();
export let data: $$Props['data'];
$$restProps;
const { colorStore } = data; const { colorStore } = data;
</script> </script>
@@ -14,13 +10,13 @@
<div class="custom"> <div class="custom">
<Handle type="target" position={Position.Left} /> <Handle type="target" position={Position.Left} />
<div> <div>
Custom Color Picker Node: <strong>{$colorStore}</strong> Custom Color Picker Node: <strong>{bg.color}</strong>
</div> </div>
<input <input
class="nodrag" class="nodrag"
type="color" type="color"
on:input={(evt) => colorStore.set(evt.currentTarget.value)} oninput={(evt) => (bg.color = evt.currentTarget.value)}
value={$colorStore} value={bg.color}
/> />
<Handle type="source" position={Position.Right} id="a" style="top: 20px;" /> <Handle type="source" position={Position.Right} id="a" style="top: 20px;" />
<Handle type="source" position={Position.Right} id="b" style="top: auto; bottom: 10px;" /> <Handle type="source" position={Position.Right} id="b" style="top: auto; bottom: 10px;" />
@@ -1,79 +1,77 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store'; import { SvelteFlow, Background, Position, ConnectionLineType, Panel } from '@xyflow/svelte';
import { SvelteFlow, Background, Position, ConnectionLineType, Panel } from '@xyflow/svelte'; import type { Edge, Node } from '@xyflow/svelte';
import type { Edge, Node } from '@xyflow/svelte'; import dagre from '@dagrejs/dagre';
import dagre from '@dagrejs/dagre';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
import { initialNodes, initialEdges } from './nodes-and-edges'; import { initialNodes, initialEdges } from './nodes-and-edges';
const dagreGraph = new dagre.graphlib.Graph(); const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({})); dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 172; const nodeWidth = 172;
const nodeHeight = 36; const nodeHeight = 36;
function getLayoutedElements(nodes: Node[], edges: Edge[], direction = 'TB') { function getLayoutedElements(nodes: Node[], edges: Edge[], direction = 'TB') {
const isHorizontal = direction === 'LR'; const isHorizontal = direction === 'LR';
dagreGraph.setGraph({ rankdir: direction }); dagreGraph.setGraph({ rankdir: direction });
nodes.forEach((node) => { nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
}); });
edges.forEach((edge) => { edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target); dagreGraph.setEdge(edge.source, edge.target);
}); });
dagre.layout(dagreGraph); dagre.layout(dagreGraph);
nodes.forEach((node) => { const layoutedNodes = nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id); const nodeWithPosition = dagreGraph.node(node.id);
node.targetPosition = isHorizontal ? Position.Left : Position.Top;
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
// We are shifting the dagre node position (anchor=center center) to the top left return {
// so it matches the React Flow node anchor point (top left). ...node,
node.position = { targetPosition: isHorizontal ? Position.Left : Position.Top,
x: nodeWithPosition.x - nodeWidth / 2, sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
y: nodeWithPosition.y - nodeHeight / 2 position: {
}; x: nodeWithPosition.x - nodeWidth / 2,
}); y: nodeWithPosition.y - nodeHeight / 2
}
};
});
return { nodes, edges }; return { nodes: layoutedNodes, edges };
} }
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
initialNodes, initialNodes,
initialEdges initialEdges
); );
const nodes = writable<Node[]>(layoutedNodes); let nodes = $state.raw<Node[]>(layoutedNodes);
const edges = writable<Edge[]>(layoutedEdges); let edges = $state.raw<Edge[]>(layoutedEdges);
function onLayout(direction: string) { function onLayout(direction: string) {
const layoutedElements = getLayoutedElements($nodes, $edges, direction); const layoutedElements = getLayoutedElements(nodes, edges, direction);
$nodes = layoutedElements.nodes; nodes = layoutedElements.nodes;
$edges = layoutedElements.edges; edges = layoutedElements.edges;
// nodes.set(layoutedElements.nodes); }
// edges.set(layoutedElements.edges);
}
</script> </script>
<div style="height:100vh;"> <div style="height:100vh;">
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
fitView fitView
connectionLineType={ConnectionLineType.SmoothStep} connectionLineType={ConnectionLineType.SmoothStep}
defaultEdgeOptions={{ type: 'smoothstep', animated: true }} defaultEdgeOptions={{ type: 'smoothstep', animated: true }}
> >
<Panel position="top-right"> <Panel position="top-right">
<button on:click={() => onLayout('TB')}>vertical layout</button> <button onclick={() => onLayout('TB')}>vertical layout</button>
<button on:click={() => onLayout('LR')}>horizontal layout</button> <button onclick={() => onLayout('LR')}>horizontal layout</button>
</Panel> </Panel>
<Background /> <Background />
</SvelteFlow> </SvelteFlow>
</div> </div>
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -13,7 +12,7 @@
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
const nodes = writable<Node[]>([ const initialNodes = [
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -32,9 +31,11 @@
data: { label: 'Output Node' }, data: { label: 'Output Node' },
position: { x: 300, y: 150 } position: { x: 300, y: 150 }
} }
]); ];
const edges = writable([ let nodes = $state.raw<Node[]>(initialNodes);
let edges = $state.raw([
{ {
id: '1-2', id: '1-2',
type: 'default', type: 'default',
@@ -50,7 +51,7 @@
} }
]); ]);
const svelteFlow = useSvelteFlow(); const { screenToFlowPosition } = useSvelteFlow();
const onDragOver = (event: DragEvent) => { const onDragOver = (event: DragEvent) => {
event.preventDefault(); event.preventDefault();
@@ -68,7 +69,7 @@
} }
const type = event.dataTransfer.getData('application/svelteflow') || 'default'; const type = event.dataTransfer.getData('application/svelteflow') || 'default';
const position = svelteFlow.screenToFlowPosition({ const position = screenToFlowPosition({
x: event.clientX, x: event.clientX,
y: event.clientY y: event.clientY
}); });
@@ -79,17 +80,14 @@
data: { label: `${type} node` } data: { label: `${type} node` }
}; };
$nodes.push(newNode); nodes = [...nodes, newNode];
$nodes = $nodes;
}; };
$: { $inspect(nodes);
console.log($nodes);
}
</script> </script>
<main> <main>
<SvelteFlow {nodes} {edges} fitView on:dragover={onDragOver} on:drop={onDrop}> <SvelteFlow bind:nodes bind:edges fitView ondragover={onDragOver} ondrop={onDrop}>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -1,6 +1,4 @@
<script lang="ts"> <script lang="ts">
import { useSvelteFlow } from '@xyflow/svelte';
const onDragStart = (event: DragEvent, nodeType: string) => { const onDragStart = (event: DragEvent, nodeType: string) => {
if (!event.dataTransfer) { if (!event.dataTransfer) {
return null; return null;
@@ -9,29 +7,28 @@
event.dataTransfer.setData('application/svelteflow', nodeType); event.dataTransfer.setData('application/svelteflow', nodeType);
event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.effectAllowed = 'move';
}; };
const { zoomIn, zoomOut, fitView, viewport, nodes } = useSvelteFlow();
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<aside> <aside>
<div class="label">You can drag these nodes to the pane on the left.</div> <div class="label">You can drag these nodes to the pane on the left.</div>
<div <div
class="input-node node" class="input-node node"
on:dragstart={(event) => onDragStart(event, 'input')} ondragstart={(event) => onDragStart(event, 'input')}
draggable={true} draggable={true}
> >
Input Node Input Node
</div> </div>
<div <div
class="default-node node" class="default-node node"
on:dragstart={(event) => onDragStart(event, 'default')} ondragstart={(event) => onDragStart(event, 'default')}
draggable={true} draggable={true}
> >
Default Node Default Node
</div> </div>
<div <div
class="output-node node" class="output-node node"
on:dragstart={(event) => onDragStart(event, 'output')} ondragstart={(event) => onDragStart(event, 'output')}
draggable={true} draggable={true}
> >
Output Node Output Node
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -7,14 +6,15 @@
BackgroundVariant, BackgroundVariant,
MiniMap, MiniMap,
MarkerType, MarkerType,
type Connection type Connection,
type EdgeTypes
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
import ButtonEdge from './ButtonEdge.svelte'; import ButtonEdge from './ButtonEdge.svelte';
import CustomBezierEdge from './CustomBezierEdge.svelte'; import CustomBezierEdge from './CustomBezierEdge.svelte';
const nodes = writable([ let nodes = $state.raw([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -54,7 +54,7 @@
} }
]); ]);
const edges = writable([ let edges = $state.raw([
{ {
id: 'e1-2', id: 'e1-2',
source: '1', source: '1',
@@ -128,45 +128,29 @@
target: '6', target: '6',
label: 'hi', label: 'hi',
labelStyle: 'background: red; font-weight: 700; padding: 5px;', labelStyle: 'background: red; font-weight: 700; padding: 5px;',
style: 'stroke: #ffcc0', style: 'stroke: #ffcc0'
markerEnd: {
type: MarkerType.Arrow,
color: '#FFCC00',
markerUnits: 'userSpaceOnUse',
width: 20,
height: 20,
strokeWidth: 2
},
markerStart: {
type: MarkerType.ArrowClosed,
color: '#FFCC00',
orient: 'auto-start-reverse',
markerUnits: 'userSpaceOnUse',
width: 20,
height: 20
}
} }
]); ]);
const edgeTypes = { const edgeTypes: EdgeTypes = {
button: ButtonEdge, button: ButtonEdge,
customBezier: CustomBezierEdge customBezier: CustomBezierEdge
}; };
function getEdgeId(connection: Connection) { function getEdgeId(connection: Connection) {
return `edge-${connection.source}-${connection.target}}`; return `edge-${connection.source}-${connection.target}}`;
} }
$: console.log('edges', $edges); $inspect(edges);
</script> </script>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
{edgeTypes} {edgeTypes}
fitView fitView
nodeDragThreshold={2} nodeDragThreshold={2}
onedgecreate={(connection) => { onbeforeconnect={(connection) => {
console.log('on edge create', connection); console.log('on edge create', connection);
return { return {
@@ -1,59 +1,80 @@
<script lang="ts"> <script lang="ts">
import { getBezierPath, BaseEdge, type EdgeProps, EdgeLabelRenderer } from '@xyflow/svelte'; import {
getBezierPath,
BaseEdge,
type EdgeProps,
useSvelteFlow,
MarkerType,
EdgeReconnectAnchor,
EdgeLabel
} from '@xyflow/svelte';
type $$Props = EdgeProps; let {
style,
export let id: $$Props['id'] = ''; markerEnd,
export let style: $$Props['style'] = undefined;
export let markerEnd: $$Props['markerEnd'] = undefined;
export let sourceX: $$Props['sourceX'];
export let sourceY: $$Props['sourceY'];
export let sourcePosition: $$Props['sourcePosition'];
export let targetX: $$Props['targetX'];
export let targetY: $$Props['targetY'];
export let targetPosition: $$Props['targetPosition'];
$$restProps;
$: [edgePath, labelX, labelY] = getBezierPath({
sourceX, sourceX,
sourceY, sourceY,
sourcePosition, sourcePosition,
targetX, targetX,
targetY, targetY,
targetPosition targetPosition,
}); selected
}: EdgeProps = $props();
let [edgePath, labelX, labelY] = $derived(
getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition
})
);
const { updateEdge } = useSvelteFlow();
let reconnecting = $state(false);
</script> </script>
<BaseEdge path={edgePath} {markerEnd} {style} /> {#if !reconnecting}
<EdgeLabelRenderer> <BaseEdge path={edgePath} {markerEnd} {style} />
<div <EdgeLabel x={labelX} y={labelY}>
class="edgeButtonContainer nodrag nopan"
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
>
<button <button
class="edgeButton" class="edgeButton"
on:click={(event) => { onclick={(event) => {
event.stopPropagation(); event.stopPropagation();
alert(`remove ${id}`); updateEdge('e5-6', {
markerEnd: {
type: MarkerType.Arrow,
color: '#FFCC00',
markerUnits: 'userSpaceOnUse',
width: 20,
height: 20,
strokeWidth: 2
},
markerStart: {
type: MarkerType.ArrowClosed,
color: '#FFCC00',
orient: 'auto-start-reverse',
markerUnits: 'userSpaceOnUse',
width: 20,
height: 20
}
});
}} }}
> >
× ×
</button> </button>
</div> </EdgeLabel>
</EdgeLabelRenderer> {/if}
{#if selected}
<EdgeReconnectAnchor bind:reconnecting type="source" position={{ x: sourceX, y: sourceY }} />
<EdgeReconnectAnchor bind:reconnecting type="target" position={{ x: targetX, y: targetY }} />
{/if}
<style> <style>
.edgeButtonContainer {
position: absolute;
font-size: 12pt;
/* everything inside EdgeLabelRenderer has no pointer events by default */
/* if you have an interactive element, set pointer-events: all */
pointer-events: all;
}
.edgeButton { .edgeButton {
width: 20px; width: 20px;
height: 20px; height: 20px;
@@ -61,8 +82,7 @@
border: 1px solid #fff; border: 1px solid #fff;
cursor: pointer; cursor: pointer;
border-radius: 50%; border-radius: 50%;
font-size: 12px; font-size: 10px;
line-height: 1;
} }
.edgeButton:hover { .edgeButton:hover {
@@ -1,38 +1,7 @@
<script lang="ts"> <script lang="ts">
import { BezierEdge, type EdgeProps } from '@xyflow/svelte'; import { BezierEdge, type EdgeProps } from '@xyflow/svelte';
type $$Props = EdgeProps; let { ...props }: EdgeProps = $props();
export let label: $$Props['label'] = undefined;
export let labelStyle: $$Props['labelStyle'] = undefined;
export let style: $$Props['style'] = undefined;
export let markerStart: $$Props['markerStart'] = undefined;
export let markerEnd: $$Props['markerEnd'] = undefined;
export let interactionWidth: $$Props['interactionWidth'] = undefined;
export let sourceX: $$Props['sourceX'];
export let sourceY: $$Props['sourceY'];
export let sourcePosition: $$Props['sourcePosition'];
export let targetX: $$Props['targetX'];
export let targetY: $$Props['targetY'];
export let targetPosition: $$Props['targetPosition'];
$$restProps;
</script> </script>
<BezierEdge <BezierEdge {...props} pathOptions={{ curvature: 1 }} />
{sourceX}
{sourceY}
{sourcePosition}
{targetX}
{targetY}
{targetPosition}
{label}
{labelStyle}
{markerStart}
{markerEnd}
{style}
{interactionWidth}
pathOptions={{ curvature: 1 }}
/>
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -17,11 +16,11 @@
const panOnDrag = [1, 2]; const panOnDrag = [1, 2];
const onMoveStart = (e: any) => console.log('move start', e); const onmovestart = (e: any) => console.log('move start', e);
const onMove = (e: any) => console.log('move', e); const onmove = (e: any) => console.log('move', e);
const onMoveEnd = (e: any) => console.log('move end', e); const onmoveend = (e: any) => console.log('move end', e);
const nodes = writable([ let nodes = $state.raw([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -34,23 +33,23 @@
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' } { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' }
]); ]);
const edges = writable([ let edges = $state.raw([
{ id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' } { id: 'e1-3', source: '1', target: '3' }
]); ]);
</script> </script>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
fitView fitView
selectionMode={SelectionMode.Partial} selectionMode={SelectionMode.Partial}
selectionOnDrag selectionOnDrag
panOnScroll panOnScroll
{panOnDrag} {panOnDrag}
{onMoveStart} {onmovestart}
{onMove} {onmove}
{onMoveEnd} {onmoveend}
> >
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -20,7 +19,7 @@
multi: MultiHandleNode multi: MultiHandleNode
}; };
const nodes = writable<Node[]>([ let nodes = $state.raw<Node[]>([
{ {
id: '1', id: '1',
type: 'single', type: 'single',
@@ -60,7 +59,7 @@
} }
]); ]);
const edges = writable<Edge[]>([ let edges = $state.raw<Edge[]>([
{ {
id: 'e1-2', id: 'e1-2',
source: '1', source: '1',
@@ -87,7 +86,7 @@
]); ]);
</script> </script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView colorMode="dark"> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView colorMode="dark">
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -4,12 +4,10 @@
Position, Position,
type NodeProps, type NodeProps,
type Connection, type Connection,
useHandleConnections useNodeConnections
} from '@xyflow/svelte'; } from '@xyflow/svelte';
type $$Props = NodeProps; let { id }: NodeProps = $props();
export let id: $$Props['id'];
function onConnectTarget(connection: Connection[]) { function onConnectTarget(connection: Connection[]) {
console.log('connect target', connection); console.log('connect target', connection);
@@ -27,13 +25,18 @@
console.log('disconnect source', handleId, connection); console.log('disconnect source', handleId, connection);
} }
const connections = useHandleConnections({ nodeId: id, type: 'target' }); const connections = useNodeConnections({
id: id,
handleType: 'target',
onConnect: (connections) => {
console.log('onConnect', connections);
},
onDisconnect: (connections) => {
console.log('onDisconnect', connections);
}
});
$: { $inspect(connections.current);
console.log('connections', id, $connections);
}
$$restProps;
</script> </script>
<div class="custom"> <div class="custom">
@@ -1,9 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type NodeProps, type Connection } from '@xyflow/svelte'; import { Handle, Position, type NodeProps, type Connection } from '@xyflow/svelte';
type $$Props = NodeProps; let { id }: NodeProps = $props();
export let id: $$Props['id'];
function onConnectTarget(connection: Connection[]) { function onConnectTarget(connection: Connection[]) {
console.log('connect target', connection); console.log('connect target', connection);
@@ -20,8 +18,6 @@
function onDisconnectSource(connection: Connection[]) { function onDisconnectSource(connection: Connection[]) {
console.log('disconnect source', connection); console.log('disconnect source', connection);
} }
$$restProps;
</script> </script>
<div class="custom"> <div class="custom">
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -13,7 +12,7 @@
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
const nodes = writable([ let nodes = $state.raw([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -25,37 +24,37 @@
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } } { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }
]); ]);
const edges = writable([ let edges = $state.raw([
{ id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' } { id: 'e1-3', source: '1', target: '3' }
]); ]);
const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node); const onnodedragstart = (_: MouseEvent, node: Node) => console.log('drag start', node);
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onnodedragstop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onnodeclick = (_: MouseEvent, node: Node) => console.log('click', node);
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); const onedgeclick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
const onPaneClick = (event: MouseEvent) => console.log('onPaneClick', event); const onpaneclick = (event: MouseEvent) => console.log('onPaneClick', event);
const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event); const onpanescroll = (event?: WheelEvent) => console.log('onPaneScroll', event);
const onPaneContextMenu = (event: MouseEvent) => console.log('onPaneContextMenu', event); const onpanecontextmenu = (event: MouseEvent) => console.log('onPaneContextMenu', event);
const onMoveEnd: OnMoveEnd = (_, viewport) => console.log('onMoveEnd', viewport); const onmoveend: OnMoveEnd = (_, viewport) => console.log('onMoveEnd', viewport);
let isSelectable = false; let isSelectable = $state(false);
let isDraggable = false; let isDraggable = $state(false);
let isConnectable = false; let isConnectable = $state(false);
let zoomOnScroll = false; let zoomOnScroll = $state(false);
let zoomOnPinch = false; let zoomOnPinch = $state(false);
let panOnScroll = false; let panOnScroll = $state(false);
let panOnScrollMode = PanOnScrollMode.Free; let panOnScrollMode = $state(PanOnScrollMode.Free);
let zoomOnDoubleClick = false; let zoomOnDoubleClick = $state(false);
let panOnDrag = true; let panOnDrag = $state(true);
let captureZoomClick = false; let captureZoomClick = $state(false);
let captureZoomScroll = false; let captureZoomScroll = $state(false);
let captureElementClick = false; let captureElementClick = $state(false);
</script> </script>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
elementsSelectable={isSelectable} elementsSelectable={isSelectable}
nodesConnectable={isConnectable} nodesConnectable={isConnectable}
nodesDraggable={isDraggable} nodesDraggable={isDraggable}
@@ -65,7 +64,8 @@
{panOnScrollMode} {panOnScrollMode}
{zoomOnDoubleClick} {zoomOnDoubleClick}
{panOnDrag} {panOnDrag}
{onMoveEnd} {onmoveend}
fitView
> >
<MiniMap /> <MiniMap />
<Controls /> <Controls />
@@ -143,7 +143,7 @@
<select <select
id="panonscrollmode" id="panonscrollmode"
bind:value={panOnScrollMode} bind:value={panOnScrollMode}
on:change={(event) => { onchange={(event) => {
panOnScrollMode = PanOnScrollMode.Free; panOnScrollMode = PanOnScrollMode.Free;
}} }}
class="svelte-flow__panonscrollmode" class="svelte-flow__panonscrollmode"
@@ -1,31 +1,34 @@
<script lang="ts"> <script lang="ts">
import { SvelteFlow, Background, Controls, useSvelteFlow } from '@xyflow/svelte'; import { SvelteFlow, Background, Controls, useSvelteFlow } from '@xyflow/svelte';
import type { Edge, Node } from '@xyflow/svelte'; import type { Edge, Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
import { initialNodes, initialEdges } from './nodes-and-edges'; import { initialNodes, initialEdges } from './nodes-and-edges';
const nodes = writable<Node[]>(initialNodes); let nodes = $state.raw<Node[]>(initialNodes);
const edges = writable<Edge[]>(initialEdges); let edges = $state.raw<Edge[]>(initialEdges);
const { getIntersectingNodes } = useSvelteFlow(); const { getIntersectingNodes } = useSvelteFlow();
function onNodeDrag({ detail: { targetNode } }) {
if (targetNode) {
const intersections = getIntersectingNodes(targetNode).map((n) => n.id);
$nodes.forEach((n) => {
n.class = intersections.includes(n.id) ? 'highlight' : '';
});
$nodes = $nodes;
}
}
</script> </script>
<div style="height:100vh;"> <div style="height:100vh;">
<SvelteFlow {nodes} {edges} fitView class="intersection-flow" on:nodedrag={onNodeDrag}> <SvelteFlow
bind:nodes
bind:edges
fitView
class="intersection-flow"
onnodedrag={({ targetNode }) => {
if (targetNode) {
const intersections = getIntersectingNodes(targetNode).map((n) => n.id);
nodes = nodes.map((node) => ({
...node,
class: intersections.includes(node.id) ? 'highlight' : ''
}));
}
}}
>
<Background /> <Background />
<Controls /> <Controls />
</SvelteFlow> </SvelteFlow>
@@ -48,10 +51,10 @@
} }
:global( :global(
.intersection-flow .svelte-flow__node.selected, .intersection-flow .svelte-flow__node.selected,
.intersection-flow .svelte-flow__node:hover, .intersection-flow .svelte-flow__node:hover,
.intersection-flow .svelte-flow__node:focus .intersection-flow .svelte-flow__node:focus
) { ) {
box-shadow: 6px 6px 0 1px rgba(0, 0, 0, 0.7); box-shadow: 6px 6px 0 1px rgba(0, 0, 0, 0.7);
background-color: #eee; background-color: #eee;
} }
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Panel, type Edge } from '@xyflow/svelte'; import { SvelteFlow, Controls, Panel, type Edge } from '@xyflow/svelte';
import DefaultResizer from './DefaultResizer.svelte'; import DefaultResizer from './DefaultResizer.svelte';
@@ -19,9 +18,9 @@
const nodeStyle = 'border: 1px solid #222; font-size: 10px; background-color: #ddd;'; const nodeStyle = 'border: 1px solid #222; font-size: 10px; background-color: #ddd;';
const edges = writable<Edge[]>([]); let edges = $state.raw<Edge[]>([]);
const nodes = writable<ResizeNode[]>([ let nodes = $state.raw<ResizeNode[]>([
{ {
id: '1', id: '1',
type: 'defaultResizer', type: 'defaultResizer',
@@ -128,12 +127,12 @@
} }
]); ]);
let snapToGrid = false; let snapToGrid = $state(false);
</script> </script>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
{nodeTypes} {nodeTypes}
minZoom={0.2} minZoom={0.2}
maxZoom={5} maxZoom={5}
@@ -144,7 +143,7 @@
<Controls /> <Controls />
<Panel position="bottom-right"> <Panel position="bottom-right">
<button <button
on:click={() => { onclick={() => {
snapToGrid = !snapToGrid; snapToGrid = !snapToGrid;
}} }}
> >
@@ -1,10 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type NodeProps, NodeResizeControl } from '@xyflow/svelte'; import { Handle, Position, type NodeProps, NodeResizeControl, type Node } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>; let { data }: NodeProps<ResizeNode> = $props();
export let data: $$Props['data'];
</script> </script>
<NodeResizeControl <NodeResizeControl
@@ -2,10 +2,7 @@
import { Handle, NodeResizer, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, NodeResizer, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>; let { data, selected }: NodeProps<ResizeNode> = $props();
export let data: $$Props['data'];
export let selected: $$Props['selected'] = undefined;
</script> </script>
<NodeResizer <NodeResizer
@@ -2,9 +2,7 @@
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>; let { data }: NodeProps<ResizeNode> = $props();
export let data: $$Props['data'];
</script> </script>
<NodeResizeControl <NodeResizeControl
@@ -2,9 +2,7 @@
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>; let { data }: NodeProps<ResizeNode> = $props();
export let data: $$Props['data'];
</script> </script>
<NodeResizeControl <NodeResizeControl
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Background, Background,
@@ -11,7 +10,7 @@
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
import CustomNode from './CustomNode.svelte'; import CustomNode, { type CustomNodeType } from './CustomNode.svelte';
import SelectedNodesToolbar from './SelectedNodesToolbar.svelte'; import SelectedNodesToolbar from './SelectedNodesToolbar.svelte';
const nodeTypes: NodeTypes = { const nodeTypes: NodeTypes = {
@@ -19,9 +18,9 @@
}; };
const positions = ['top', 'right', 'bottom', 'left']; const positions = ['top', 'right', 'bottom', 'left'];
const alignments = ['start', 'center', 'end']; const alignments: ('start' | 'center' | 'end')[] = ['start', 'center', 'end'];
const initialNodes: Node[] = [ const initialNodes: CustomNodeType[] = [
{ {
id: 'default-node', id: 'default-node',
type: 'custom', type: 'custom',
@@ -51,12 +50,12 @@
const initialEdges: Edge[] = []; const initialEdges: Edge[] = [];
const nodes = writable<Node[]>(initialNodes); let nodes = $state.raw<Node[]>(initialNodes);
const edges = writable<Edge[]>(initialEdges); let edges = $state.raw<Edge[]>(initialEdges);
</script> </script>
<div style="height: 100vh;"> <div style="height: 100vh;">
<SvelteFlow {nodes} {edges} {nodeTypes} fitView> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView>
<Background /> <Background />
<SelectedNodesToolbar /> <SelectedNodesToolbar />
</SvelteFlow> </SvelteFlow>
@@ -1,10 +1,18 @@
<script lang="ts" module>
import { type Node } from '@xyflow/svelte';
export type CustomNodeType = Node<{
toolbarVisible?: boolean;
toolbarPosition: Position;
toolbarAlign?: 'start' | 'center' | 'end';
label: string;
}>;
</script>
<script lang="ts"> <script lang="ts">
import { NodeToolbar, type NodeProps, Handle, Position } from '@xyflow/svelte'; import { NodeToolbar, type NodeProps, Handle, Position } from '@xyflow/svelte';
type $$Props = NodeProps; let { data }: NodeProps<CustomNodeType> = $props();
$$restProps;
export let data: $$Props['data'];
</script> </script>
<NodeToolbar <NodeToolbar
@@ -1,10 +1,12 @@
<script lang="ts"> <script lang="ts">
import { NodeToolbar, useNodes } from '@xyflow/svelte'; import { NodeToolbar, useNodes } from '@xyflow/svelte';
const nodes = useNodes(); let nodes = useNodes();
$: selectedNodeIds = $nodes.filter((node) => node.selected).map((node) => node.id); let selectedNodeIds = $derived(
$: isVisible = selectedNodeIds.length > 1; nodes.current.filter((node) => node.selected).map((node) => node.id)
);
let isVisible = $derived(selectedNodeIds.length > 1);
</script> </script>
<NodeToolbar nodeId={selectedNodeIds} {isVisible}> <NodeToolbar nodeId={selectedNodeIds} {isVisible}>
@@ -1,240 +1,8 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store'; import { SvelteFlowProvider } from '@xyflow/svelte';
import { import Flow from './Flow.svelte';
SvelteFlow,
Controls,
Background,
BackgroundVariant,
MiniMap,
Panel,
SelectionMode,
type NodeTypes,
type EdgeTypes,
type Node,
type Edge,
ConnectionMode,
ControlButton,
type FitViewOptions
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
import CustomNodeDragHandle from './CustomNodeDragHandle.svelte';
import CustomEdge from './CustomEdge.svelte';
import '@xyflow/svelte/dist/style.css';
import InitTracker from './InitTracker.svelte';
const nodeTypes: NodeTypes = {
custom: CustomNode,
dragHandle: CustomNodeDragHandle
};
const edgeTypes: EdgeTypes = {
custom: CustomEdge
};
const fitViewOptions: FitViewOptions = {
padding: 0.2,
nodes: [{ id: '1' }, { id: '2' }]
};
const nodes = writable<Node[]>([
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 150, y: 5 }
},
{
id: '2',
type: 'default',
data: { label: 'Node' },
position: { x: 0, y: 150 },
selectable: false
},
{
id: 'A',
type: 'default',
data: { label: 'Styled with class' },
class: 'custom-style',
position: { x: 150, y: 150 }
},
{
id: 'D',
type: 'default',
data: { label: 'Not draggable' },
position: { x: 150, y: 200 },
draggable: false
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 300, y: 150 }
},
{
id: 'B',
type: 'default',
data: { label: 'Styled with style' },
style: 'border: 2px solid #ff5050;',
position: { x: 450, y: 150 }
},
{
id: 'C',
type: 'dragHandle',
data: { label: 'custom drag handle' },
dragHandle: '.custom-drag-handle',
position: { x: 450, y: 250 }
},
{
id: '4',
type: 'custom',
data: { label: 'Custom Node' },
position: { x: 150, y: 300 }
},
{
id: 'hideunhide',
data: { label: 'HIDE ME' },
position: { x: 300, y: 75 },
hidden: true
}
]);
const edges = writable<Edge[]>([
{
id: '1-2',
type: 'default',
source: '1',
target: '2',
label: 'Edge Text'
},
{
id: '1-3',
type: 'smoothstep',
source: '1',
target: '3',
selectable: false
},
{
id: '2-4',
type: 'custom',
source: '2',
target: '4',
animated: true
}
]);
function updateNode() {
$nodes[0].position.x += 20;
$nodes = $nodes;
}
function updateEdge() {
$edges[0].type = $edges[0].type === 'default' ? 'smoothstep' : 'default';
$edges = $edges;
}
$: {
console.log('nodes changed', $nodes);
}
</script> </script>
<SvelteFlow <SvelteFlowProvider>
{nodes} <Flow />
{edges} </SvelteFlowProvider>
{nodeTypes}
{edgeTypes}
fitView
fitViewOptions={{
padding: 0.1,
nodes: [{ id: '1' }, { id: '2' }, { id: '3' }]
}}
minZoom={0}
maxZoom={Infinity}
selectionMode={SelectionMode.Full}
initialViewport={{ x: 100, y: 100, zoom: 2 }}
snapGrid={[25, 25]}
oninit={() => console.log('on init')}
on:nodeclick={(event) => console.log('on node click', event)}
on:nodemouseenter={(event) => console.log('on node enter', event)}
on:nodemouseleave={(event) => console.log('on node leave', event)}
on:edgeclick={(event) => console.log('edge click', event)}
on:edgemouseenter={(event) => console.log('edge enter', event)}
on:edgemouseleave={(event) => console.log('edge leave', event)}
onconnectstart={(event) => console.log('on connect start', event)}
onconnect={(event) => console.log('on connect', event)}
onconnectend={(event) => console.log('on connect end', event)}
on:paneclick={(event) => console.log('on pane click', event)}
on:panecontextmenu={(event) => {
console.log('on pane contextmenu', event);
}}
on:nodedrag={(event) => {
console.log('on node drag', event);
}}
on:nodedragstart={(event) => {
console.log('on node drag start', event);
}}
on:nodedragstop={(event) => {
console.log('on node drag stop', event);
}}
on:nodecontextmenu={(event) => {
event.detail.event.preventDefault();
console.log('on node contextmenu', event);
}}
on:edgecontextmenu={({ detail: { event, edge } }) => {
event.preventDefault();
console.log('on edge contextmenu', edge);
}}
on:selectionclick={(event) => console.log('on selection click', event)}
on:selectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
onbeforedelete={async ({ nodes, edges }) => {
console.log('on before delete', nodes, edges);
const deleteElements = confirm('Are you sure you want to delete the selected elements?');
return deleteElements;
}}
autoPanOnConnect
autoPanOnNodeDrag
connectionMode={ConnectionMode.Strict}
attributionPosition={'top-center'}
deleteKey={['Backspace', 'd']}
>
<Controls orientation="horizontal" {fitViewOptions}>
<ControlButton slot="before">xy</ControlButton>
<ControlButton aria-label="log" on:click={() => console.log('control button')}
>log</ControlButton
>
</Controls>
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Panel position="top-right">
<button on:click={updateNode}>update node pos</button>
<button on:click={updateEdge}>update edge type</button>
<button
on:click={() => {
console.log($nodes, $nodes.length);
$nodes[$nodes.length - 1].hidden = !$nodes[$nodes.length - 1].hidden;
$nodes = $nodes;
}}>hide/unhide</button
>
</Panel>
<InitTracker />
</SvelteFlow>
<style>
:global(.svelte-flow .custom-style) {
background: #ff5050;
color: white;
}
:root {
--background-color: #ffffdd;
--background-pattern-color: #5050ff;
--minimap-background-color: #f5f6f7;
--minimap-mask-color: rgb(255, 255, 240, 0.6);
--controls-button-background-color: #ddd;
--controls-button-background-color-hover: #ccc;
}
</style>
@@ -1,46 +1,35 @@
<script lang="ts"> <script lang="ts">
import { import { BaseEdge, useEdges, getBezierPath, type EdgeProps, EdgeLabel } from '@xyflow/svelte';
BaseEdge,
EdgeLabelRenderer,
useEdges,
type EdgeProps,
getBezierPath
} from '@xyflow/svelte';
type $$Props = EdgeProps; let { ...props }: EdgeProps = $props();
$: [path, labelX, labelY] = getBezierPath({ let [path, labelX, labelY] = $derived(
sourceX: $$props.sourceX, getBezierPath({
sourceY: $$props.sourceY, sourceX: props.sourceX,
targetX: $$props.targetX, sourceY: props.sourceY,
targetY: $$props.targetY, targetX: props.targetX,
sourcePosition: $$props.sourcePosition, targetY: props.targetY,
targetPosition: $$props.targetPosition sourcePosition: props.sourcePosition,
}); targetPosition: props.targetPosition
})
);
const edges = useEdges(); const edges = useEdges();
function onClick() { function onClick() {
edges.update((eds) => eds.filter((e) => e.id !== $$props.id)); edges.current = edges.current.filter((e) => e.id !== props.id);
} }
</script> </script>
<BaseEdge {path} {labelX} {labelY} {...$$props} /> <BaseEdge {path} {labelX} {labelY} {...props} />
<EdgeLabelRenderer> <EdgeLabel x={labelX} y={labelY} selectEdgeOnClick>
<button <button class="edge-button" onclick={onClick}> </button>
style:transform={`translate(-50%,-50%) translate(${labelX}px,${labelY}px)`} </EdgeLabel>
class="edge-button"
on:click={onClick}
>
</button>
</EdgeLabelRenderer>
<style> <style>
.edge-button { .edge-button {
border-radius: 50%; border-radius: 50%;
position: absolute;
border: none; border: none;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
@@ -48,7 +37,6 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 8px; font-size: 8px;
pointer-events: all;
cursor: pointer; cursor: pointer;
background-color: #eee; background-color: #eee;
} }
@@ -1,12 +1,21 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type BuiltInNode, type NodeProps } from '@xyflow/svelte'; import {
Handle,
Position,
useOnSelectionChange,
type BuiltInNode,
type NodeProps
} from '@xyflow/svelte';
type $$Props = NodeProps<BuiltInNode>; let {
$$restProps; data = { label: 'Node' },
positionAbsoluteX = 0,
positionAbsoluteY = 0
}: NodeProps<BuiltInNode> = $props();
export let data: { label: string } = { label: 'Node' }; useOnSelectionChange(({ nodes, edges }) => {
export let positionAbsoluteX: number = 0; console.log('on selection changed via hook', { nodes, edges });
export let positionAbsoluteY: number = 0; });
</script> </script>
<div class="custom"> <div class="custom">
@@ -1,15 +1,12 @@
<script lang="ts"> <script lang="ts">
import type { BuiltInNode, NodeProps } from '@xyflow/svelte'; import type { BuiltInNode, NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps<BuiltInNode>; let { data = { label: 'Node' } }: NodeProps<BuiltInNode> = $props();
$$restProps;
export let data: { label: string } = { label: 'Node' };
</script> </script>
<div class="custom"> <div class="custom">
<div>{data.label}</div> <div>{data.label}</div>
<div class="custom-drag-handle" style="width:15px;height:15px;background:#222;" /> <div class="custom-drag-handle" style="width:15px;height:15px;background:#222;"></div>
</div> </div>
<style> <style>
@@ -0,0 +1,261 @@
<script lang="ts">
import {
SvelteFlow,
Controls,
Background,
BackgroundVariant,
MiniMap,
Panel,
SelectionMode,
type NodeTypes,
type EdgeTypes,
type Node,
type Edge,
ConnectionMode,
ControlButton,
type FitViewOptions,
useSvelteFlow,
Position
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
import CustomNodeDragHandle from './CustomNodeDragHandle.svelte';
import CustomEdge from './CustomEdge.svelte';
import '@xyflow/svelte/dist/style.css';
import InitTracker from './InitTracker.svelte';
const nodeTypes: NodeTypes = {
custom: CustomNode,
dragHandle: CustomNodeDragHandle
};
const edgeTypes: EdgeTypes = {
custom: CustomEdge
};
const fitViewOptions: FitViewOptions = {
padding: 0.2,
nodes: [{ id: '1' }, { id: '2' }]
};
let nodes = $state.raw<Node[]>([
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 150, y: 5 }
},
{
id: '2',
type: 'default',
data: { label: 'Node' },
position: { x: 0, y: 150 },
selectable: false
},
{
id: 'A',
type: 'default',
data: { label: 'Styled with class' },
class: 'custom-style',
position: { x: 150, y: 150 }
},
{
id: 'D',
type: 'default',
data: { label: 'Not draggable' },
position: { x: 150, y: 200 },
draggable: false
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 300, y: 150 }
},
{
id: 'B',
type: 'default',
data: { label: 'Styled with style' },
style: 'border: 2px solid #ff5050;',
height: 55,
position: { x: 450, y: 150 }
},
{
id: 'C',
type: 'dragHandle',
data: { label: 'custom drag handle' },
dragHandle: '.custom-drag-handle',
position: { x: 450, y: 250 }
},
{
id: '4',
type: 'custom',
data: { label: 'Custom Node' },
position: { x: 150, y: 300 }
},
{
id: 'hideunhide',
data: { label: 'HIDE ME' },
position: { x: 300, y: 75 },
hidden: true
}
]);
let edges = $state.raw<Edge[]>([
{
id: '1-2',
type: 'default',
source: '1',
target: '2',
label: 'Edge Text'
},
{
id: '1-3',
type: 'smoothstep',
source: '1',
target: '3',
selectable: false
},
{
id: '2-4',
type: 'custom',
source: '2',
target: '4',
animated: true
}
]);
function moveNode() {
// We might as well just use updateNode() here
// this is just to show what is required to update a node
const newNode = { ...nodes[0] };
newNode.position.x += 20;
nodes[0] = newNode;
nodes = [...nodes];
}
function changeEdgeType() {
// We might as well just use updateEdge() here
// this is just to show what is required to update an edge
const newEdge = { ...edges[0] };
newEdge.type = newEdge.type === 'default' ? 'smoothstep' : 'default';
edges[0] = newEdge;
edges = [...edges];
}
let { updateNode } = useSvelteFlow();
function hideUnhide() {
updateNode('hideunhide', (node) => ({ hidden: !node.hidden }));
}
// $inspect(edges);
$effect(() => {
// console.log(edges.map((edge) => ({ id: edge.id, selected: edge.selected })));
edges.forEach((edge) => console.log({ id: edge.id, selected: edge.selected }));
});
</script>
<!-- oninit={() => console.log('on init')}
onnodeclick={(event) => console.log('on node click', event)}
onnodepointerenter={(event) => console.log('on node enter', event)}
onnodepointerleave={(event) => console.log('on node leave', event)}
onedgeclick={(event) => console.log('edge click', event)}
onedgepointerenter={(event) => console.log('edge enter', event)}
onedgepointerleave={(event) => console.log('edge leave', event)}
onconnectstart={(event) => console.log('on connect start', event)}
onconnect={(event) => console.log('on connect', event)}
onconnectend={(event) => console.log('on connect end', event)}
onpaneclick={(event) => console.log('on pane click', event)}
onpanecontextmenu={(event) => {
console.log('on pane contextmenu', event);
}}
onnodedrag={(event) => {
console.log('on node drag', event);
}}
onnodedragstart={(event) => {
console.log('on node drag start', event);
}}
onnodedragstop={({ event }) => {
console.log('on node drag stop', event);
}}
onnodecontextmenu={({ event }) => {
event.preventDefault();
console.log('on node contextmenu', event);
}}
onedgecontextmenu={({ event, edge }) => {
event.preventDefault();
console.log('on edge contextmenu', edge);
}}
onselectionclick={(event) => console.log('on selection click', event)}
onselectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
onbeforedelete={async ({ nodes, edges }) => {
console.log('on before delete', nodes, edges);
const deleteElements = confirm('Are you sure you want to delete the selected elements?');
return deleteElements;
}} -->
<SvelteFlow
bind:nodes
bind:edges
{nodeTypes}
{edgeTypes}
fitView
fitViewOptions={{
padding: 0.1,
nodes: [{ id: '1' }, { id: '2' }, { id: '3' }]
}}
minZoom={0}
maxZoom={Infinity}
selectionMode={SelectionMode.Full}
initialViewport={{ x: 100, y: 100, zoom: 2 }}
snapGrid={[25, 25]}
autoPanOnConnect
autoPanOnNodeDrag
connectionMode={ConnectionMode.Strict}
attributionPosition={'top-center'}
deleteKey={['Backspace', 'd']}
onselectionchange={({ nodes, edges }) => {
console.log('on selection changed via prop', { nodes, edges });
}}
selectNodesOnDrag
>
<Controls orientation="horizontal" {fitViewOptions}>
{#snippet before()}
<ControlButton>xy</ControlButton>
{/snippet}
<ControlButton aria-label="log" onclick={() => console.log('control button')}>log</ControlButton
>
</Controls>
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Panel position="top-right">
<button onclick={moveNode}>update node pos</button>
<button onclick={changeEdgeType}>update edge type</button>
<button onclick={hideUnhide}>hide/unhide</button>
<button
onclick={() => {
nodes = nodes.map((node) => ({
...node,
sourcePosition: Position.Right,
targetPosition: Position.Left
}));
}}>left-right</button
>
</Panel>
<InitTracker />
</SvelteFlow>
<style>
:root {
/* --background-color: #ffffdd; */
--background-pattern-color: #5050ff;
--xy-minimap-background-color: red;
--xy-minimap-mask-color: rgb(255, 255, 240, 0.6);
--controls-button-background-color: #ddd;
--controls-button-background-color-hover: #ccc;
}
</style>
@@ -1,18 +1,18 @@
<script lang="ts"> <script lang="ts">
import { useNodesInitialized, useInitialized } from '@xyflow/svelte'; import { useNodesInitialized, useViewportInitialized } from '@xyflow/svelte';
const nodesInitialized = useNodesInitialized(); const nodesInitialized = useNodesInitialized();
const initialized = useInitialized(); const viewportInitialized = useViewportInitialized();
$: { $effect(() => {
if (nodesInitialized) { if (viewportInitialized.current) {
console.log('viewportInitialized');
}
});
$effect(() => {
if (nodesInitialized.current) {
console.log('nodes initialized'); console.log('nodes initialized');
} }
} });
$: {
if (initialized) {
console.log('initialized');
}
}
</script> </script>
@@ -1,16 +1,17 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Background, MiniMap, type Node, type Edge } from '@xyflow/svelte'; import { SvelteFlow, Controls, Background, MiniMap, type Node, type Edge } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
const nodes = writable<Node[]>([]); let nodes = $state.raw<Node[]>([]);
const edges = writable<Edge[]>([]); let edges = $state.raw<Edge[]>([]);
const resetNodesArray = () => { const resetNodesArray = () => {
const node1: Node = { const node1: Node = {
id: 'c1', id: 'c1',
data: { label: '1' }, data: { label: '1' },
width: 100,
height: 100,
position: { x: 0, y: 0 } position: { x: 0, y: 0 }
}; };
@@ -20,14 +21,14 @@
position: { x: 100, y: 0 } position: { x: 100, y: 0 }
}; };
$nodes = [node1, node2]; nodes = [node1, node2];
}; };
</script> </script>
<button on:click={resetNodesArray}> Reset </button> <button onclick={resetNodesArray}> Reset </button>
<div style:height="100vh"> <div style:height="100vh">
<SvelteFlow {nodes} {edges} fitView> <SvelteFlow bind:nodes bind:edges fitView>
<Controls /> <Controls />
<Background /> <Background />
<MiniMap /> <MiniMap />
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -29,7 +28,8 @@
id, id,
data, data,
position, position,
type: 'default' type: 'default',
selected: true
}; };
nodeItems.push(node); nodeItems.push(node);
@@ -46,11 +46,11 @@
} }
} }
const nodes = writable(nodeItems); let nodes = $state.raw(nodeItems);
const edges = writable(edgeItems); let edges = $state.raw(edgeItems);
</script> </script>
<SvelteFlow {nodes} {edges} fitView minZoom={0.2}> <SvelteFlow bind:nodes bind:edges fitView minZoom={0.2} onlyRenderVisibleElements>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Lines} /> <Background variant={BackgroundVariant.Lines} />
<MiniMap /> <MiniMap />
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -7,7 +6,8 @@
BackgroundVariant, BackgroundVariant,
MiniMap, MiniMap,
type NodeTypes, type NodeTypes,
type Node type Node,
type Edge
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
@@ -18,7 +18,7 @@
default: DebugNode default: DebugNode
}; };
const nodes = writable<Node[]>([ let nodes = $state.raw<Node[]>([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -95,7 +95,7 @@
} }
]); ]);
const edges = writable([ let edges = $state.raw<Edge[]>([
{ {
id: 'e1-2', id: 'e1-2',
source: '1', source: '1',
@@ -115,13 +115,9 @@
{ id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 }, { id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 },
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' } { id: 'e4b1-4b2', source: '4b1', target: '4b2' }
]); ]);
$: {
console.log($nodes);
}
</script> </script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView minZoom={0.1} maxZoom={2.5}> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView minZoom={0.1} maxZoom={2.5}>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -3,12 +3,23 @@
type $$Props = NodeProps; type $$Props = NodeProps;
export let id: string; interface Props {
export let positionAbsoluteX: number = 0; id: string;
export let positionAbsoluteY: number = 0; positionAbsoluteX?: number;
export let zIndex: number = 0; positionAbsoluteY?: number;
zIndex?: number;
[key: string]: any
}
$$restProps; let {
id,
positionAbsoluteX = 0,
positionAbsoluteY = 0,
zIndex = 0,
...rest
}: Props = $props();
rest;
</script> </script>
<Handle type="target" position={Position.Top} /> <Handle type="target" position={Position.Top} />
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -12,7 +11,7 @@
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
const nodes = writable([ let nodes = $state.raw([
{ {
id: 'A', id: 'A',
position: { x: 0, y: 0 }, position: { x: 0, y: 0 },
@@ -20,27 +19,25 @@
}, },
{ id: 'B', position: { x: 0, y: 100 }, data: { label: 'B' } } { id: 'B', position: { x: 0, y: 100 }, data: { label: 'B' } }
]); ]);
const edges = writable([{ id: 'ab', source: 'A', target: 'B' }]);
const viewport = writable<Viewport>({ x: 100, y: 100, zoom: 1.25 }); let edges = $state.raw([{ id: 'ab', source: 'A', target: 'B' }]);
let viewport = $state<Viewport>({ x: 100, y: 100, zoom: 5 });
const { fitView } = useSvelteFlow(); const { fitView } = useSvelteFlow();
const updateViewport = () => { const updateViewport = () => {
$viewport.x += 10; viewport.x += 10;
$viewport = $viewport;
}; };
viewport.subscribe((vp) => { $inspect(viewport);
console.log('viewport update', vp);
});
</script> </script>
<SvelteFlow {nodes} {edges} {viewport}> <SvelteFlow bind:nodes bind:edges bind:viewport>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<Panel> <Panel>
<button on:click={updateViewport}>update viewport</button> <button onclick={updateViewport}>update viewport</button>
<button on:click={() => fitView()}>fitView</button> <button onclick={() => fitView()}>fitView</button>
</Panel> </Panel>
</SvelteFlow> </SvelteFlow>
@@ -1,19 +1,18 @@
<script context="module" lang="ts"> <script module lang="ts">
import type { Node } from '@xyflow/svelte'; import type { Node } from '@xyflow/svelte';
type TextNodeType = Node<{ text: string }, 'text'>; type TextNodeType = Node<{ text: string }, 'text'>;
type UppercaseNodeType = Node<{ text: string }, 'uppercase'>; type UppercaseNodeType = Node<{ text: string }, 'uppercase'>;
type ResultNodeType = Node<{}, 'result'>; type ResultNodeType = Node<{}, 'result'>;
export function isTextNode(node: any): node is TextNodeType | UppercaseNode { export function isTextNode(node: any): node is TextNodeType | UppercaseNodeType {
return node.type === 'text' || node.type === 'uppercase'; return !node || !node.type ? false : node.type === 'text' || node.type === 'uppercase';
} }
export type MyNode = TextNodeType | UppercaseNodeType | ResultNodeType; export type MyNode = TextNodeType | UppercaseNodeType | ResultNodeType;
</script> </script>
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -35,7 +34,7 @@
result: ResultNode result: ResultNode
}; };
const nodes = writable<MyNode[]>([ let nodes = $state.raw<MyNode[]>([
{ {
id: '1', id: '1',
type: 'text', type: 'text',
@@ -60,7 +59,6 @@
}, },
position: { x: 0, y: 100 } position: { x: 0, y: 100 }
}, },
{ {
id: '3', id: '3',
type: 'result', type: 'result',
@@ -69,7 +67,7 @@
} }
]); ]);
const edges = writable<Edge[]>([ let edges = $state.raw<Edge[]>([
{ {
id: 'e1-1a', id: 'e1-1a',
source: '1', source: '1',
@@ -88,7 +86,7 @@
]); ]);
</script> </script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -2,24 +2,24 @@
import { import {
Handle, Handle,
Position, Position,
useHandleConnections, useNodeConnections,
useNodesData, useNodesData,
type NodeProps type NodeProps,
type Node
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import { isTextNode, type MyNode } from './+page.svelte'; import { isTextNode, type MyNode } from './+page.svelte';
type $$Props = NodeProps; let { id }: NodeProps = $props();
export let id: $$Props['id']; const connections = useNodeConnections({
$$restProps; id: id,
handleType: 'target'
const connections = useHandleConnections({
nodeId: id,
type: 'target'
}); });
$: nodeData = useNodesData<MyNode>($connections.map((connection) => connection.source)); let nodeData = $derived(
$: textNodes = $nodeData.filter(isTextNode); useNodesData<MyNode>(connections.current.map((connection) => connection.source))
);
let textNodes = $derived(nodeData.current.filter(isTextNode));
</script> </script>
<div class="custom"> <div class="custom">
@@ -1,13 +1,9 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type NodeProps, useSvelteFlow } from '@xyflow/svelte'; import { Handle, Position, type NodeProps, useSvelteFlow } from '@xyflow/svelte';
type $$Props = NodeProps; let { id, data }: NodeProps = $props();
export let id: $$Props['id'];
export let data: $$Props['data'];
const { updateNodeData } = useSvelteFlow(); const { updateNodeData } = useSvelteFlow();
$$restProps;
</script> </script>
<div class="custom"> <div class="custom">
@@ -15,7 +11,7 @@
<div> <div>
<input <input
value={data.text} value={data.text}
on:input={(evt) => updateNodeData(id, { text: evt.currentTarget.value })} oninput={(evt) => updateNodeData(id, { text: evt.currentTarget.value })}
/> />
</div> </div>
<Handle type="source" position={Position.Right} /> <Handle type="source" position={Position.Right} />
@@ -2,39 +2,33 @@
import { import {
Handle, Handle,
Position, Position,
useHandleConnections, useNodeConnections,
useNodesData, useNodesData,
useSvelteFlow, useSvelteFlow,
type Node,
type NodeProps type NodeProps
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import { isTextNode, type MyNode } from './+page.svelte'; import { isTextNode, type MyNode } from './+page.svelte';
type $$Props = NodeProps; let { id }: NodeProps<Node<{ text: string }>> = $props();
export let id: $$Props['id'];
export let data: $$Props['data'];
$$restProps;
const { updateNodeData } = useSvelteFlow(); const { updateNodeData } = useSvelteFlow();
const connections = useHandleConnections({ const connections = useNodeConnections({
nodeId: id, id: id,
type: 'target' handleType: 'target'
}); });
$: nodeData = useNodesData<MyNode>($connections[0]?.source); let nodeData = $derived(useNodesData<MyNode>(connections.current[0]?.source));
$: textNode = isTextNode($nodeData) ? $nodeData : null; let textNodeData = $derived(isTextNode(nodeData.current) ? nodeData.current.data.text : null);
$: console.log(textNode?.data, data); $effect.pre(() => {
const input = textNodeData?.toUpperCase() ?? '';
$: {
const input = textNode?.data.text.toUpperCase() ?? '';
updateNodeData(id, { text: input }); updateNodeData(id, { text: input });
console.log('updatedNodeData with', input); });
}
</script> </script>
<div class="custom"> <div class="custom">
<Handle type="target" position={Position.Left} isConnectable={$connections.length === 0} /> <Handle type="target" position={Position.Left} isConnectable={connections.current.length === 0} />
<div>uppercase transform</div> <div>uppercase transform</div>
<Handle type="source" position={Position.Right} /> <Handle type="source" position={Position.Right} />
</div> </div>
@@ -1,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Background, BackgroundVariant, MiniMap } from '@xyflow/svelte'; import { SvelteFlow, Controls, Background, BackgroundVariant, MiniMap } from '@xyflow/svelte';
import Sidebar from './Sidebar.svelte'; import Sidebar from './Sidebar.svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
const nodes = writable([ let nodes = $state.raw([
{ {
id: '1', id: '1',
type: 'input', type: 'input',
@@ -27,7 +26,7 @@
} }
]); ]);
const edges = writable([ let edges = $state.raw([
{ {
id: '1-2', id: '1-2',
type: 'default', type: 'default',
@@ -45,7 +44,7 @@
</script> </script>
<main> <main>
<SvelteFlow {nodes} {edges} fitView> <SvelteFlow bind:nodes bind:edges fitView maxZoom={4}>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { useEdges, useNodes, useSvelteFlow } from '@xyflow/svelte'; import { useEdges, useNodes, useSvelteFlow, useViewport } from '@xyflow/svelte';
const { const {
zoomIn, zoomIn,
@@ -9,53 +9,62 @@
setCenter, setCenter,
setViewport, setViewport,
getViewport, getViewport,
viewport,
toObject, toObject,
deleteElements deleteElements
} = useSvelteFlow(); } = useSvelteFlow();
const nodes = useNodes(); let nodes = useNodes();
const edges = useEdges(); let edges = useEdges();
let viewport = useViewport();
const deleteNode = () => { const deleteNode = () => {
$nodes.shift(); //TODO: do we really want to allow this?
$nodes = $nodes; // nodes.shift();
nodes.current.shift();
nodes.current = [...nodes.current];
}; };
</script> </script>
<aside> <aside>
<div class="label">Functions:</div> <div class="label">Functions:</div>
<button on:click={() => zoomIn()}>zoom in</button> <button onclick={() => zoomIn()}>zoom in</button>
<button on:click={() => zoomOut({ duration: 1000 })}>zoom out transition</button> <button onclick={() => zoomOut({ duration: 1000 })}>zoom out transition</button>
<button on:click={() => setZoom(2)}>set zoom</button> <button onclick={() => setZoom(2)}>set zoom</button>
<button on:click={() => fitView()}>fitView</button> <button onclick={() => fitView({ duration: 600 })}>fitView</button>
<button on:click={() => setCenter(0, 0)}>setCenter 0, 0</button> <button onclick={() => fitView({ duration: 600, ease: (t) => +t, interpolate: 'linear' })}
<button on:click={() => setViewport({ x: 100, y: 100, zoom: 2 })}>setViewport</button> >fitView linear</button
<button on:click={() => console.log(getViewport())}>getViewport</button> >
<button onclick={() => setCenter(0, 0)}>setCenter 0, 0</button>
<button onclick={() => setViewport({ x: 100, y: 100, zoom: 2 })}>setViewport</button>
<button onclick={() => console.log(getViewport())}>getViewport</button>
<button on:click={() => deleteElements({ edges: $edges.map((edge) => ({ id: edge.id })) })} <button onclick={() => deleteElements({ edges: edges.current.map((edge) => ({ id: edge.id })) })}
>delete edges</button >delete edges</button
> >
<button on:click={() => deleteElements({ nodes: [{ id: $nodes[0].id }] })}>delete node</button> <button onclick={() => deleteElements({ nodes: [{ id: nodes.current[0].id }] })}
<button on:click={() => deleteElements({ nodes: $nodes.map((node) => ({ id: node.id })) })} >delete node</button
>
<button onclick={() => deleteElements({ nodes: nodes.current.map((node) => ({ id: node.id })) })}
>deleteElements</button >deleteElements</button
> >
<button on:click={() => deleteNode()}>delete via store</button> <button onclick={() => deleteNode()}>delete via store</button>
<button <button
on:click={() => { onclick={() => {
const { nodes, edges, viewport } = toObject(); const { nodes, edges, viewport } = toObject();
console.log(nodes, edges, viewport); console.log(nodes, edges, viewport);
}}>toObject</button }}>toObject</button
> >
<div class="label">Nodes:</div> <div class="label">Nodes:</div>
{#each $nodes as node (node.id)} {#each nodes.current as node (node.id)}
<div>id: {node.id} | x: {node.position.x.toFixed(1)} y: {node.position.y.toFixed(1)}</div> <div>id: {node.id} | x: {node.position.x.toFixed(1)} y: {node.position.y.toFixed(1)}</div>
{/each} {/each}
<div class="label">Viewport:</div> <div class="label">Viewport:</div>
<div> <div>
x: {$viewport.x.toFixed(1)} y: {$viewport.y.toFixed(1)} zoom: {$viewport.zoom.toFixed(1)} x: {viewport.current.x.toFixed(1)} y: {viewport.current.y.toFixed(1)} zoom: {viewport.current.zoom.toFixed(
1
)}
</div> </div>
</aside> </aside>
@@ -1,23 +1,20 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type NodeProps, useUpdateNodeInternals } from '@xyflow/svelte'; import { Handle, Position, type NodeProps, useUpdateNodeInternals } from '@xyflow/svelte';
type $$Props = NodeProps; let { id }: NodeProps = $props();
export let id: $$Props['id'];
$$restProps;
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
$: handleCount = 1; let handleCount = $state(1);
const onClick = () => { const onClick = () => {
handleCount += 1; handleCount += 1;
updateNodeInternals(id); updateNodeInternals();
}; };
</script> </script>
<Handle type="target" position={Position.Top} /> <Handle type="target" position={Position.Top} />
<button on:click={onClick}>add handle</button> <button onclick={onClick}>add handle</button>
{#each Array.from({ length: handleCount }) as handle, i} {#each Array.from({ length: handleCount }) as handle, i}
<Handle type="source" position={Position.Bottom} id={`${i}`} style={`left: ${i * 10}px;`} /> <Handle type="source" position={Position.Bottom} id={`${i}`} style={`left: ${i * 10}px;`} />
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -19,7 +18,7 @@
custom: CustomNode custom: CustomNode
}; };
const nodes = writable([ let nodes = $state.raw([
{ {
id: '1', id: '1',
type: 'custom', type: 'custom',
@@ -40,7 +39,7 @@
} }
]); ]);
const edges = writable([ let edges = $state.raw([
{ {
id: '1-2', id: '1-2',
source: '1', source: '1',
@@ -56,17 +55,19 @@
const { updateNode } = useSvelteFlow(); const { updateNode } = useSvelteFlow();
const updateNodePosition = () => { const updateNodePosition = () => {
updateNode('1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } })); updateNode('1', (node) => ({
position: { x: node.position.x + 10, y: node.position.y }
}));
}; };
</script> </script>
<main> <main>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView>
<Controls /> <Controls />
<Background variant={BackgroundVariant.Dots} /> <Background variant={BackgroundVariant.Dots} />
<MiniMap /> <MiniMap />
<Panel><button on:click={updateNodePosition}>update node</button></Panel> <Panel><button onclick={updateNodePosition}>update node</button></Panel>
</SvelteFlow> </SvelteFlow>
</main> </main>
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store';
import { import {
SvelteFlow, SvelteFlow,
Controls, Controls,
@@ -17,7 +16,7 @@
targetPosition: Position.Left targetPosition: Position.Left
}; };
const nodes = writable([ let nodes = $state.raw([
{ {
id: '0', id: '0',
position: { x: 0, y: 150 }, position: { x: 0, y: 150 },
@@ -29,7 +28,7 @@
{ id: 'C', position: { x: 250, y: 300 }, data: { label: 'C' }, ...nodeDefaults } { id: 'C', position: { x: 250, y: 300 }, data: { label: 'C' }, ...nodeDefaults }
]); ]);
const edges = writable([]); let edges = $state.raw([]);
const isValidConnection: IsValidConnection = (connection) => connection.target === 'B'; const isValidConnection: IsValidConnection = (connection) => connection.target === 'B';
@@ -39,8 +38,8 @@
</script> </script>
<SvelteFlow <SvelteFlow
{nodes} bind:nodes
{edges} bind:edges
fitView fitView
minZoom={0.1} minZoom={0.1}
maxZoom={2.5} maxZoom={2.5}
@@ -3,7 +3,11 @@
import Flow from './Flow.svelte'; import Flow from './Flow.svelte';
export let data: { flowConfig: FlowConfig }; let {
data
}: {
data: { flowConfig: FlowConfig };
} = $props();
</script> </script>
<SvelteFlowProvider> <SvelteFlowProvider>
@@ -1,19 +1,25 @@
<script lang="ts"> <script lang="ts">
import { writable } from 'svelte/store'; import {
import { Background, Controls, MiniMap, Panel, SvelteFlow } from '@xyflow/svelte'; Background,
Controls,
MiniMap,
Panel,
SvelteFlow,
type SvelteFlowProps
} from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
export let flowConfig: FlowConfig; let { flowConfig }: { flowConfig: FlowConfig } = $props();
// Create writables here so it is easier to create test cases // Create writables here so it is easier to create test cases
const nodes = writable(flowConfig.flowProps?.nodes); let nodes = $state.raw(flowConfig.flowProps?.nodes ?? []);
const edges = writable(flowConfig.flowProps?.edges); let edges = $state.raw(flowConfig.flowProps?.edges ?? []);
const flowProps = { ...flowConfig.flowProps, nodes, edges }; const flowProps: SvelteFlowProps = { ...flowConfig.flowProps };
</script> </script>
<SvelteFlow {...flowProps}> <SvelteFlow {...flowProps} bind:nodes bind:edges>
{#if flowConfig.panelProps} {#if flowConfig.panelProps}
<Panel {...flowConfig.panelProps} /> <Panel {...flowConfig.panelProps} />
{/if} {/if}
+4 -2
View File
@@ -5,8 +5,10 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess({ script: true }),
compilerOptions: {
runes: true
},
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // 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. // If your environment is not supported or you settled on a specific environment, switch out the adapter.
+4 -1
View File
@@ -8,7 +8,10 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true,
"target": "ES2022",
"verbatimModuleSyntax": true,
"isolatedModules": true
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// //
+4
View File
@@ -3,6 +3,10 @@ import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit()],
build: {
sourcemap: true,
minify: false
},
server: { server: {
fs: { fs: {
// Allow serving files from one level up to the project root // Allow serving files from one level up to the project root
+1 -6
View File
@@ -25,20 +25,15 @@
"@changesets/changelog-github": "^0.4.7", "@changesets/changelog-github": "^0.4.7",
"@changesets/cli": "^2.25.0", "@changesets/cli": "^2.25.0",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"concurrently": "^7.6.0", "concurrently": "^7.6.0",
"eslint": "^8.22.0", "eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "latest",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^4.18.0", "rollup": "^4.18.0",
"turbo": "^2.0.3", "turbo": "^2.0.3",
"typescript": "5.1.3" "typescript": "5.4.5"
}, },
"packageManager": "pnpm@9.2.0" "packageManager": "pnpm@9.2.0"
} }
+339
View File
@@ -1,5 +1,344 @@
# @xyflow/react # @xyflow/react
## 12.6.4
### Patch Changes
- [#5263](https://github.com/xyflow/xyflow/pull/5263) [`e4a8d4b4`](https://github.com/xyflow/xyflow/commit/e4a8d4b43fdd3a9f493db758bdf02e1a10c6e640) Thanks [@moklick](https://github.com/moklick)! - Multi select key works when input is focused
- [#5266](https://github.com/xyflow/xyflow/pull/5266) [`77107453`](https://github.com/xyflow/xyflow/commit/77107453fa6f34cb08ef91640b8b02d58e31275e) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix connection snapping for handles larger than connectionRadius
- Updated dependencies [[`77107453`](https://github.com/xyflow/xyflow/commit/77107453fa6f34cb08ef91640b8b02d58e31275e)]:
- @xyflow/system@0.0.61
## 12.6.3
### Patch Changes
- [#5259](https://github.com/xyflow/xyflow/pull/5259) [`77bf79c4`](https://github.com/xyflow/xyflow/commit/77bf79c40e71e3da449ace3b1a1ed5bceff46b51) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix background-color css variable fallback.
- Updated dependencies [[`77bf79c4`](https://github.com/xyflow/xyflow/commit/77bf79c40e71e3da449ace3b1a1ed5bceff46b51)]:
- @xyflow/system@0.0.60
## 12.6.2
### Patch Changes
- [#5257](https://github.com/xyflow/xyflow/pull/5257) [`b1314be0`](https://github.com/xyflow/xyflow/commit/b1314be04dee07e7589a8c99c9c953006788a152) Thanks [@moklick](https://github.com/moklick)! - Use OnReconnect from system
- Updated dependencies [[`a95f0e2f`](https://github.com/xyflow/xyflow/commit/a95f0e2fbfc2d070d9bd70b753d1606a87332e3f)]:
- @xyflow/system@0.0.59
## 12.6.1
### Patch Changes
- [#5249](https://github.com/xyflow/xyflow/pull/5249) [`895b5d81`](https://github.com/xyflow/xyflow/commit/895b5d81c8ee5236009820ecd0ed6806c6e59e29) Thanks [@moklick](https://github.com/moklick)! - Call `onNodesChange` for uncontrolled flows that use `updateNode`
- [#5247](https://github.com/xyflow/xyflow/pull/5247) [`67e1cb68`](https://github.com/xyflow/xyflow/commit/67e1cb6891078dbcb9e1d06b9f9fdbfc79860ab6) Thanks [@moklick](https://github.com/moklick)! - Cleanup TSDoc annotations for ReactFlow
- Updated dependencies [[`2a03213b`](https://github.com/xyflow/xyflow/commit/2a03213b0695d504f831579ec9df3f9de2d3e0bd)]:
- @xyflow/system@0.0.58
## 12.6.0
### Minor Changes
- [#5219](https://github.com/xyflow/xyflow/pull/5219) [`4236adbc`](https://github.com/xyflow/xyflow/commit/4236adbc462b3f65ee41869ef426491ed3fa8ba0) Thanks [@moklick](https://github.com/moklick)! - Add initialMinZoom, initialMaxZoom and initialFitViewOptions to ReactFlowProvider
- [#5227](https://github.com/xyflow/xyflow/pull/5227) [`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35) Thanks [@moklick](https://github.com/moklick)! - Add `resizeDirection` prop for the `NodeResizeControl` component
### Patch Changes
- [#5217](https://github.com/xyflow/xyflow/pull/5217) [`bce74e88`](https://github.com/xyflow/xyflow/commit/bce74e8811a98c967b5bd06c3e5aecde24c8b679) Thanks [@moklick](https://github.com/moklick)! - Keep node seleciton on pane click if elementsSelectable=false
- Updated dependencies [[`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35), [`4e681f9c`](https://github.com/xyflow/xyflow/commit/4e681f9c529c3f4f8b2ac5d25b4db7878c197e14)]:
- @xyflow/system@0.0.57
## 12.5.6
### Patch Changes
- [#5212](https://github.com/xyflow/xyflow/pull/5212) [`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13) Thanks [@moklick](https://github.com/moklick)! - Add polyfill for `Promise.withResolvers`
- [#5192](https://github.com/xyflow/xyflow/pull/5192) [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7) Thanks [@peterkogo](https://github.com/peterkogo)! - Optimize performance of nodesInitialized
- [#5196](https://github.com/xyflow/xyflow/pull/5196) [`7f902db4`](https://github.com/xyflow/xyflow/commit/7f902db46b6f1ea4adc94390db8d5db47f8c5903) Thanks [@moklick](https://github.com/moklick)! - Hide edge marker and attribution for screenreaders
- [#5213](https://github.com/xyflow/xyflow/pull/5213) [`78782c16`](https://github.com/xyflow/xyflow/commit/78782c1696323ee9be0d38bbd807bd3fde5f549d) Thanks [@moklick](https://github.com/moklick)! - Do not trigger selection events if elementsSelectable=false
- [#5191](https://github.com/xyflow/xyflow/pull/5191) [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix legacy padding being slightly larger than before
- Updated dependencies [[`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13), [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7), [`98fe23c7`](https://github.com/xyflow/xyflow/commit/98fe23c7c2b12972f1b7def866215ce82a86e2c0), [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e)]:
- @xyflow/system@0.0.56
## 12.5.5
### Patch Changes
- [#5172](https://github.com/xyflow/xyflow/pull/5172) [`e6139a00`](https://github.com/xyflow/xyflow/commit/e6139a00d4414ba2c1d3e500cdfa67d7e66e655a) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `useNodesData`, `useReactFlow`, `isNode` and `isEdge`
- [#5165](https://github.com/xyflow/xyflow/pull/5165) [`d536abea`](https://github.com/xyflow/xyflow/commit/d536abea9240bad7f5c1064efc0a4713ebef87d1) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `useViewport`, `useUpdateNodeInternals`, `useOnSelectionChange`, `useNodesInitialized` hooks and `UseOnSelectionChangeOptions`, `UseNodesInitializedOptions` types
- [#5171](https://github.com/xyflow/xyflow/pull/5171) [`62d87409`](https://github.com/xyflow/xyflow/commit/62d874097337a022bebe54b559834c0d582f435e) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `ReactFlowProps`
- [#5154](https://github.com/xyflow/xyflow/pull/5154) [`d0237166`](https://github.com/xyflow/xyflow/commit/d02371662669aab91cd2ac7c45b412491c3377bd) Thanks [@ibagov](https://github.com/ibagov)! - Improve TSDoc comments for `onNodesChange`
- [#5174](https://github.com/xyflow/xyflow/pull/5174) [`ae585d13`](https://github.com/xyflow/xyflow/commit/ae585d136e34c9e9ad9d45f75c059bc5367e73ae) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `BaseEdgeProps`
- [#5159](https://github.com/xyflow/xyflow/pull/5159) [`0c1436d6`](https://github.com/xyflow/xyflow/commit/0c1436d6a371240cfa0adecea573c44fd42df7b3) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `useConnection` hook
- [#5167](https://github.com/xyflow/xyflow/pull/5167) [`934ea42d`](https://github.com/xyflow/xyflow/commit/934ea42d9af6027a3164e1196a78140bdd05d347) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `useEdges`, `useInternalNode`, `useNodes` and `useNodeId` hooks
- [#5163](https://github.com/xyflow/xyflow/pull/5163) [`ab800054`](https://github.com/xyflow/xyflow/commit/ab800054a50c68f9c27dfad2b0d3833b782f4797) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `useNodesState` and `useEdgesState` hook
- [#5160](https://github.com/xyflow/xyflow/pull/5160) [`b357f43d`](https://github.com/xyflow/xyflow/commit/b357f43dfa262205059ac9714198196b4aaf8870) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type UseHandleConnectionsParams` and `useHandleConnections` hook
- [#5162](https://github.com/xyflow/xyflow/pull/5162) [`29f4aeb2`](https://github.com/xyflow/xyflow/commit/29f4aeb260df0a5d83e775c7c2ed788f997006a7) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type UseNodeConnectionsParams` and `useNodeConnections` hook
- [#5164](https://github.com/xyflow/xyflow/pull/5164) [`09021550`](https://github.com/xyflow/xyflow/commit/09021550dc72ac240fcbb6adb3cf530d91575f79) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type UseOnViewportChangeOptions` and `useOnViewportChange` hook
- [#5166](https://github.com/xyflow/xyflow/pull/5166) [`701ad17e`](https://github.com/xyflow/xyflow/commit/701ad17ed3f523389e7d0bf9a006bef249645c3d) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `useStore` hook
- [#5170](https://github.com/xyflow/xyflow/pull/5170) [`eb2a33c6`](https://github.com/xyflow/xyflow/commit/eb2a33c6dfb0629e851ab3a0f2cd70eca92efc42) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `interface GetSimpleBezierPathParams` and `getSimpleBezierPath`
- [#5161](https://github.com/xyflow/xyflow/pull/5161) [`c4efe749`](https://github.com/xyflow/xyflow/commit/c4efe749208e854dc9ced5bdd00933269d5b4382) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type UseKeyPressOptions` and `useKeyPress` hook
- Updated dependencies [[`02a3b746`](https://github.com/xyflow/xyflow/commit/02a3b74645799a3f0ce670b69365fa86ecb0616e), [`cbe305e1`](https://github.com/xyflow/xyflow/commit/cbe305e15a5c5d3b92583e0ec12364b2509f49bd), [`1f671bd4`](https://github.com/xyflow/xyflow/commit/1f671bd48f06230da841fdd1d7a312413ef16d03), [`aaebc462`](https://github.com/xyflow/xyflow/commit/aaebc462951ded8e91374c3e084d77af5ed7380a), [`6ec942fc`](https://github.com/xyflow/xyflow/commit/6ec942fc6501f81009c278cc995764bef3e8d03b)]:
- @xyflow/system@0.0.55
## 12.5.4
### Patch Changes
- [#5134](https://github.com/xyflow/xyflow/pull/5134) [`7acab1e1`](https://github.com/xyflow/xyflow/commit/7acab1e123944c296180fbf826a3fd488963608f) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Adjust `@default` TSDoc tags for `BackgroundProps`
- [#5135](https://github.com/xyflow/xyflow/pull/5135) [`754a1671`](https://github.com/xyflow/xyflow/commit/754a167134f22fa00a139de4c7e10aaaa1953ac8) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `NodeResizerProps` and `ResizeControlProps`
- [#5140](https://github.com/xyflow/xyflow/pull/5140) [`82e6860e`](https://github.com/xyflow/xyflow/commit/82e6860e1354b8bb8047399b7773fd090be206d7) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `MiniMapProps` and `PanelProps`
- [#5147](https://github.com/xyflow/xyflow/pull/5147) [`f819005b`](https://github.com/xyflow/xyflow/commit/f819005be362d044b16ce4c0b85432f3f300a13a) Thanks [@moklick](https://github.com/moklick)! - Pass dimensions to final resize change event
- [#5143](https://github.com/xyflow/xyflow/pull/5143) [`b1e1cc11`](https://github.com/xyflow/xyflow/commit/b1e1cc1125d106cf1521a1524286404483e38f30) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `ReactFlowInstance` and `GeneralHelpers`
- [#5138](https://github.com/xyflow/xyflow/pull/5138) [`d4eb8d52`](https://github.com/xyflow/xyflow/commit/d4eb8d52d0e26e9534ec5fc347211ce91d1ddd32) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `ViewportHelperFunctions` and `NodeToolbarProps`
- [#5144](https://github.com/xyflow/xyflow/pull/5144) [`5a1ce56e`](https://github.com/xyflow/xyflow/commit/5a1ce56e8ce83f01e01ef531d90c52181c3e3a1a) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Compare `nodeStrokeWidth` with `number`, not with `string` within `MiniMap`
- [#5141](https://github.com/xyflow/xyflow/pull/5141) [`06cf4c10`](https://github.com/xyflow/xyflow/commit/06cf4c10f5d8a43f57ee0fde19d9a3fe1044cf48) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `Edge`, `BaseEdgeProps` and `ConnectionLineComponentProps`
- Updated dependencies [[`f819005b`](https://github.com/xyflow/xyflow/commit/f819005be362d044b16ce4c0b85432f3f300a13a), [`24a1bc89`](https://github.com/xyflow/xyflow/commit/24a1bc89348817ed9b5c87f74bf2519c705143be), [`36657cd6`](https://github.com/xyflow/xyflow/commit/36657cd66322c911e87eb37275c584a80025adfe), [`89de9ca8`](https://github.com/xyflow/xyflow/commit/89de9ca83fbf9263a687a0f5f915efb2beb31654), [`2ac6e155`](https://github.com/xyflow/xyflow/commit/2ac6e155e35256ca436281df16344366e7d05761), [`f0f378e5`](https://github.com/xyflow/xyflow/commit/f0f378e5b6918c2c30d9dc1e32587063cb942d4e)]:
- @xyflow/system@0.0.54
## 12.5.3
### Patch Changes
- [#5132](https://github.com/xyflow/xyflow/pull/5132) [`75ab8942`](https://github.com/xyflow/xyflow/commit/75ab89420e3cd0fdc34baf06eabdc50113d4de7c) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix fitView not working when onNodesChange is not defined.
## 12.5.2
### Patch Changes
- [#5124](https://github.com/xyflow/xyflow/pull/5124) [`b76f7f9e`](https://github.com/xyflow/xyflow/commit/b76f7f9eb4841f139b1468b8eda0430ddd19a1ae) Thanks [@bjornosal](https://github.com/bjornosal)! - Export NodeConnection type
- [#5127](https://github.com/xyflow/xyflow/pull/5127) [`3079c2c9`](https://github.com/xyflow/xyflow/commit/3079c2c911426f54e8d295083ddbe97ed3aad201) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix `fitView` not working when returning early in `onNodesChange`.
## 12.5.1
### Patch Changes
- [#5120](https://github.com/xyflow/xyflow/pull/5120) [`6dfea686`](https://github.com/xyflow/xyflow/commit/6dfea6863a3cbd91f932bf54a6dba549bd248bd5) Thanks [@moklick](https://github.com/moklick)! - Handle fitView for uncontrolled flows
## 12.5.0
### Minor Changes
- [#5067](https://github.com/xyflow/xyflow/pull/5067) [`acba901d`](https://github.com/xyflow/xyflow/commit/acba901d861aa84cb5beba60b24fff4cfde7ada6) Thanks [@peterkogo](https://github.com/peterkogo)! - You can now express paddings in fitViewOptions as pixels ('30px'), as viewport percentages ('20%') and define different paddings for each side.
### Patch Changes
- [#5109](https://github.com/xyflow/xyflow/pull/5109) [`0cdda42c`](https://github.com/xyflow/xyflow/commit/0cdda42cdd1cd43d43d43c44e54b7b9f7a716ca9) Thanks [@dimaMachina](https://github.com/dimaMachina)! - fix: improve TSDoc comments for `BackgroundProps`
- [#5059](https://github.com/xyflow/xyflow/pull/5059) [`065ff89d`](https://github.com/xyflow/xyflow/commit/065ff89d10488f9c76c56870511e45eaed299778) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Prevent onPaneClick when connection is in progress. Closes [#5057](https://github.com/xyflow/xyflow/issues/5057)
- [#5110](https://github.com/xyflow/xyflow/pull/5110) [`7eb6eb07`](https://github.com/xyflow/xyflow/commit/7eb6eb0709e451d7628bfdbc3ced89b3bb57b626) Thanks [@dimaMachina](https://github.com/dimaMachina)! - fix: improve TSDoc comments for `EdgeLabelOptions` and `BaseEdgeProps`
- [#5113](https://github.com/xyflow/xyflow/pull/5113) [`bce8542d`](https://github.com/xyflow/xyflow/commit/bce8542df19c33f3cd9225f483435e8a7aa4ed94) Thanks [@dimaMachina](https://github.com/dimaMachina)! - fix: improve TSDoc comments for `ControlProps`
- [#5116](https://github.com/xyflow/xyflow/pull/5116) [`58942154`](https://github.com/xyflow/xyflow/commit/589421542386906ec49d8469cac551b8f7ea1c47) Thanks [@dimaMachina](https://github.com/dimaMachina)! - fix: improve TSDoc comments for `NodeToolbarProps`
- [#5114](https://github.com/xyflow/xyflow/pull/5114) [`ba2bfbb4`](https://github.com/xyflow/xyflow/commit/ba2bfbb49aafac979f94b0136bb408faea12d5c6) Thanks [@dimaMachina](https://github.com/dimaMachina)! - feat: export `EdgeLabelRendererProps`
- [#5107](https://github.com/xyflow/xyflow/pull/5107) [`c5a8c237`](https://github.com/xyflow/xyflow/commit/c5a8c23773e5985be6b37abacdac743911be8c09) Thanks [@moklick](https://github.com/moklick)! - Add TSDoc annotations for exported edges
- [#5067](https://github.com/xyflow/xyflow/pull/5067) [`cb685281`](https://github.com/xyflow/xyflow/commit/cb685281d0eaf03e9833271c31f92b1d143af2fe) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix fitView not working immediately after adding new nodes
- [#5115](https://github.com/xyflow/xyflow/pull/5115) [`c2154557`](https://github.com/xyflow/xyflow/commit/c215455735385ef5e12e4130164b9d01f9c18aa2) Thanks [@dimaMachina](https://github.com/dimaMachina)! - fix: improve TSDoc comments for `EdgeLabelOptions` and `EdgeTextProps`
- [#5093](https://github.com/xyflow/xyflow/pull/5093) [`65825e89`](https://github.com/xyflow/xyflow/commit/65825e89a6e2e7591087eb41ac89da4da7095f8f) Thanks [@moklick](https://github.com/moklick)! - Hidden nodes are not displayed in the mini map anymore
- [#5090](https://github.com/xyflow/xyflow/pull/5090) [`8da1748a`](https://github.com/xyflow/xyflow/commit/8da1748a6ad5cdde9f03737ff786bd29c9c968de) Thanks [@moklick](https://github.com/moklick)! - Release key even when an inout field is focused
- Updated dependencies [[`5d15b01b`](https://github.com/xyflow/xyflow/commit/5d15b01ba8cb349d6397a6ed8162848b4dfec293), [`cb685281`](https://github.com/xyflow/xyflow/commit/cb685281d0eaf03e9833271c31f92b1d143af2fe), [`a79f30b3`](https://github.com/xyflow/xyflow/commit/a79f30b3dd7c8ff6400c8d22214b2c2282e5bac1)]:
- @xyflow/system@0.0.53
## 12.4.4
### Patch Changes
- [#5052](https://github.com/xyflow/xyflow/pull/5052) [`99dd7d35`](https://github.com/xyflow/xyflow/commit/99dd7d3549e7423e7d103b2c956c8b37f5747b90) Thanks [@moklick](https://github.com/moklick)! - Show an error if user drags uninitialized node
- [#5042](https://github.com/xyflow/xyflow/pull/5042) [`2fe0e850`](https://github.com/xyflow/xyflow/commit/2fe0e850a8c415c6a3113796a2c5c80e7cad2376) Thanks [@moklick](https://github.com/moklick)! - Allow click connections when target sets `isConnectableStart`
- [#5047](https://github.com/xyflow/xyflow/pull/5047) [`b3bf5693`](https://github.com/xyflow/xyflow/commit/b3bf5693c659069cea90bf1cb215ae65d06c5509) Thanks [@moklick](https://github.com/moklick)! - Pass generics to OnSelectionChangeFunc so that users can type it correctly
- [#5053](https://github.com/xyflow/xyflow/pull/5053) [`25fb45b5`](https://github.com/xyflow/xyflow/commit/25fb45b5e9d6da391b9aff652b8e6e34eaf757fc) Thanks [@moklick](https://github.com/moklick)! - Remove incorrect deprecation warning
- [#5033](https://github.com/xyflow/xyflow/pull/5033) [`7b4a81fb`](https://github.com/xyflow/xyflow/commit/7b4a81fb6b3d88f8ee7b4f070aef7ac3b962d5a6) Thanks [@dimaMachina](https://github.com/dimaMachina)! - lint: use `React.JSX` type instead of the deprecated global `JSX` namespace
- [#5043](https://github.com/xyflow/xyflow/pull/5043) [`0292ad20`](https://github.com/xyflow/xyflow/commit/0292ad20109a3b2518dc686a82e100a0a6964fb8) Thanks [@moklick](https://github.com/moklick)! - Use current expandParent value on drag to be able to update it while dragging
- [#5032](https://github.com/xyflow/xyflow/pull/5032) [`5867bba8`](https://github.com/xyflow/xyflow/commit/5867bba8050d07378a45a2026557c4bce7bda239) Thanks [@dimaMachina](https://github.com/dimaMachina)! - lint: remove unnecessary type assertions
- Updated dependencies [[`99dd7d35`](https://github.com/xyflow/xyflow/commit/99dd7d3549e7423e7d103b2c956c8b37f5747b90)]:
- @xyflow/system@0.0.52
## 12.4.3
### Patch Changes
- [#5010](https://github.com/xyflow/xyflow/pull/5010) [`6c121d42`](https://github.com/xyflow/xyflow/commit/6c121d427fea9a11e86a85f95d2c12ba8af34919) Thanks [@moklick](https://github.com/moklick)! - Add more TSDocs to components, hooks, utils funcs and types
- [#4991](https://github.com/xyflow/xyflow/pull/4991) [`ea54d9bc`](https://github.com/xyflow/xyflow/commit/ea54d9bcb197d02d248ef3e4eaabc033a43d966a) Thanks [@waynetee](https://github.com/waynetee)! - Fix viewport shifting on node focus
- [#5013](https://github.com/xyflow/xyflow/pull/5013) [`cde899c5`](https://github.com/xyflow/xyflow/commit/cde899c5be9715c4ff2cc331ea93821102604c62) Thanks [@moklick](https://github.com/moklick)! - Pass `NodeType` type argument from `ReactFlowProps` to `connectionLineComponent` property.
- [#5008](https://github.com/xyflow/xyflow/pull/5008) [`12d859fe`](https://github.com/xyflow/xyflow/commit/12d859fe297593d44cf8493a4d6bf2c664b9139c) Thanks [@moklick](https://github.com/moklick)! - Add package.json to exports
- [#5012](https://github.com/xyflow/xyflow/pull/5012) [`4d3f19e8`](https://github.com/xyflow/xyflow/commit/4d3f19e88b984ce6743970560d7367d174500f32) Thanks [@moklick](https://github.com/moklick)! - Add snapGrid option to screenToFlowPosition and set snapToGrid to false
- [#5003](https://github.com/xyflow/xyflow/pull/5003) [`e8e0d684`](https://github.com/xyflow/xyflow/commit/e8e0d684957b95d53a6cc11598c8755ff02117c7) Thanks [@dimaMachina](https://github.com/dimaMachina)! - repair lint command
- [#4991](https://github.com/xyflow/xyflow/pull/4991) [`4c62f19b`](https://github.com/xyflow/xyflow/commit/4c62f19b3afac4b3db84b14e2c36f8c9e0a96116) Thanks [@waynetee](https://github.com/waynetee)! - Prevent viewport shift after using Tab
- Updated dependencies [[`6c121d42`](https://github.com/xyflow/xyflow/commit/6c121d427fea9a11e86a85f95d2c12ba8af34919), [`4947029c`](https://github.com/xyflow/xyflow/commit/4947029cd6cda0f695e1fb4815e4030adb232234), [`e8e0d684`](https://github.com/xyflow/xyflow/commit/e8e0d684957b95d53a6cc11598c8755ff02117c7)]:
- @xyflow/system@0.0.51
## 12.4.2
### Patch Changes
- [#4957](https://github.com/xyflow/xyflow/pull/4957) [`fe843982`](https://github.com/xyflow/xyflow/commit/fe843982bfc7d7579d54772b201426b4c3f549c6) Thanks [@peterkogo](https://github.com/peterkogo)! - Narrow properties selected, selectable, deletable, draggable of NodeProps type to be required.
- Updated dependencies [[`fe843982`](https://github.com/xyflow/xyflow/commit/fe843982bfc7d7579d54772b201426b4c3f549c6), [`e73ef09f`](https://github.com/xyflow/xyflow/commit/e73ef09fbc8d872b46cf52c9d6a32dbb388c220b)]:
- @xyflow/system@0.0.50
## 12.4.1
### Patch Changes
- [#4949](https://github.com/xyflow/xyflow/pull/4949) [`592c7eaf`](https://github.com/xyflow/xyflow/commit/592c7eaf9574fc69df3123837da95f85877b23e8) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix useNodeConnection hook not returning all connected edges.
- Updated dependencies [[`592c7eaf`](https://github.com/xyflow/xyflow/commit/592c7eaf9574fc69df3123837da95f85877b23e8)]:
- @xyflow/system@0.0.49
## 12.4.0
### Minor Changes
- [#4725](https://github.com/xyflow/xyflow/pull/4725) [`e10f53cf`](https://github.com/xyflow/xyflow/commit/e10f53cf898a56f954783d6efcf6977a0d88f4a9) Thanks [@peterkogo](https://github.com/peterkogo)! - Add useNodeConnections hook to track all connections to a node. Can be filtered by handleType and handleId.
### Patch Changes
- [#4947](https://github.com/xyflow/xyflow/pull/4947) [`868aa3f3`](https://github.com/xyflow/xyflow/commit/868aa3f3db8223ea1b04a68aa027ea99fd1e91c8) Thanks [@moklick](https://github.com/moklick)! - Export ResizeControlVariant correctly as a value.
- [#4880](https://github.com/xyflow/xyflow/pull/4880) [`e2d849dc`](https://github.com/xyflow/xyflow/commit/e2d849dca63aee5952f676aef1c675c6232bb69a) Thanks [@crimx](https://github.com/crimx)! - Add type check for all event targets
- [#4929](https://github.com/xyflow/xyflow/pull/4929) [`4947f683`](https://github.com/xyflow/xyflow/commit/4947f683b7530f8e6684865ab53ea38633de0f4d) Thanks [@peterkogo](https://github.com/peterkogo)! - Optimize selections and take into account if edges connected to selected nodes are actually selectable.
- Updated dependencies [[`e2d849dc`](https://github.com/xyflow/xyflow/commit/e2d849dca63aee5952f676aef1c675c6232bb69a), [`e10f53cf`](https://github.com/xyflow/xyflow/commit/e10f53cf898a56f954783d6efcf6977a0d88f4a9), [`4947f683`](https://github.com/xyflow/xyflow/commit/4947f683b7530f8e6684865ab53ea38633de0f4d)]:
- @xyflow/system@0.0.48
## 12.3.6
### Patch Changes
- [#4846](https://github.com/xyflow/xyflow/pull/4846) [`75017939`](https://github.com/xyflow/xyflow/commit/7501793900fdb125fe906fa3d9d83f1bd11c6e20) Thanks [@moklick](https://github.com/moklick)! - Make it possible to use expandParent with immer and other immutable helpers
- [#4865](https://github.com/xyflow/xyflow/pull/4865) [`2c4acc2b`](https://github.com/xyflow/xyflow/commit/2c4acc2bd9ec271468bd4c904e19d7fca627d9e1) Thanks [@moklick](https://github.com/moklick)! - Add group node to BuiltInNode type. Thanks [@sjdemartini](https://github.com/sjdemartini)!
- [#4877](https://github.com/xyflow/xyflow/pull/4877) [`9a8309da`](https://github.com/xyflow/xyflow/commit/9a8309dab892f047fce10d3c763466cf84f726b0) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix intersections for nodes with origins other than [0,0]. Thanks [@gmvrpw](https://github.com/gmvrpw)!
- [#4844](https://github.com/xyflow/xyflow/pull/4844) [`6f11e552`](https://github.com/xyflow/xyflow/commit/6f11e552c3f51b5931b98d5ff798156cdb8cc2d9) Thanks [@moklick](https://github.com/moklick)! - Allow custom data-testid for ReactFlow component
- [#4816](https://github.com/xyflow/xyflow/pull/4816) [`43aa52a8`](https://github.com/xyflow/xyflow/commit/43aa52a8cd93e6a01c01afa314c7036eb5b77131) Thanks [@moklick](https://github.com/moklick)! - Type isValidConnection prop correctly by passing EdgeType
- [#4855](https://github.com/xyflow/xyflow/pull/4855) [`106c2cf8`](https://github.com/xyflow/xyflow/commit/106c2cf8e5e9a944ea7ea9a2558e26cb75f9ac93) Thanks [@mhuggins](https://github.com/mhuggins)! - Support passing `path` element attributes to `BaseEdge` component.
- [#4862](https://github.com/xyflow/xyflow/pull/4862) [`adf4fb4e`](https://github.com/xyflow/xyflow/commit/adf4fb4e7bb136cce47ef540e9bcc0f82cf17ff4) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Prevent default scrolling behavior when nodes or a selection is moved with an arrow key press.
- [#4875](https://github.com/xyflow/xyflow/pull/4875) [`41d4743a`](https://github.com/xyflow/xyflow/commit/41d4743a690f054f6dc2b9084b5fad8e54ade921) Thanks [@peterkogo](https://github.com/peterkogo)! - Prevent unnecessary rerenders of edges when resizing the flow.
- [#4826](https://github.com/xyflow/xyflow/pull/4826) [`5f90acda`](https://github.com/xyflow/xyflow/commit/5f90acdab1f4748bb04f8b1ec67d569983684058) Thanks [@chrtze](https://github.com/chrtze)! - Forward ref of the div inside Panel components.
- Updated dependencies [[`d60331e6`](https://github.com/xyflow/xyflow/commit/d60331e6baa7931c46af219e35c1bedbd156187c)]:
- @xyflow/system@0.0.47
## 12.3.5
### Patch Changes
- [#4789](https://github.com/xyflow/xyflow/pull/4789) [`358eb355`](https://github.com/xyflow/xyflow/commit/358eb355e0b2bea4ffa47b0f04d6edf834343cd7) Thanks [@peterkogo](https://github.com/peterkogo)! - Support key combinations which include '+' (e.g., 'Control++' resolves to the combination 'Control' and '+').
- [#4796](https://github.com/xyflow/xyflow/pull/4796) [`73402779`](https://github.com/xyflow/xyflow/commit/734027798799f4a98212dda115d33b4c54a95a45) Thanks [@Aki-7](https://github.com/Aki-7)! - Fix number of issues connected to batching node & edge updates.
- [#4790](https://github.com/xyflow/xyflow/pull/4790) [`2fa9a920`](https://github.com/xyflow/xyflow/commit/2fa9a92042ba11986abbababb7e8b294e208d6cb) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix node dragging & resizing while zooming on flow that does not cover whole browser window.
- [#4782](https://github.com/xyflow/xyflow/pull/4782) [`323e1b35`](https://github.com/xyflow/xyflow/commit/323e1b35c58bca80deb824bc8b136705593a5257) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix node intersections in nested flow.
- Updated dependencies [[`2fa9a920`](https://github.com/xyflow/xyflow/commit/2fa9a92042ba11986abbababb7e8b294e208d6cb), [`323e1b35`](https://github.com/xyflow/xyflow/commit/323e1b35c58bca80deb824bc8b136705593a5257)]:
- @xyflow/system@0.0.46
## 12.3.4
### Patch Changes
- [#4772](https://github.com/xyflow/xyflow/pull/4772) [`7f670ab0`](https://github.com/xyflow/xyflow/commit/7f670ab0423b3848a50398027297f6ec11deeaa4) Thanks [@mistic](https://github.com/mistic)! - Splits exports field in package.json to support an explicit resolution for browser, node and default to resolve issues with webpack esm module resolution.
- Updated dependencies [[`7f670ab0`](https://github.com/xyflow/xyflow/commit/7f670ab0423b3848a50398027297f6ec11deeaa4)]:
- @xyflow/system@0.0.45
## 12.3.3
### Patch Changes
- [#4755](https://github.com/xyflow/xyflow/pull/4755) [`005ae1c0`](https://github.com/xyflow/xyflow/commit/005ae1c05f6a10c1f519cd789f4f3f2fdf293bc6) Thanks [@peterkogo](https://github.com/peterkogo)! - Add module to exports in package.json. This should resolve possible issues with Webpack ESM Module Resolution.
- [#4730](https://github.com/xyflow/xyflow/pull/4730) [`2c590b90`](https://github.com/xyflow/xyflow/commit/2c590b90787aabce42de2b4108174bdf31ad6155) Thanks [@peterkogo](https://github.com/peterkogo)! - Fixed rare crash while dragging
- Updated dependencies [[`005ae1c0`](https://github.com/xyflow/xyflow/commit/005ae1c05f6a10c1f519cd789f4f3f2fdf293bc6), [`2c590b90`](https://github.com/xyflow/xyflow/commit/2c590b90787aabce42de2b4108174bdf31ad6155)]:
- @xyflow/system@0.0.44
## 12.3.2
### Patch Changes
- [#4722](https://github.com/xyflow/xyflow/pull/4722) [`e816bb69`](https://github.com/xyflow/xyflow/commit/e816bb6953486e37dd39d93252aa9b94fe5d4ec1) Thanks [@moklick](https://github.com/moklick)! - Fix internal behaviour that mutated user nodes which led to an issue with Redux and immer
## 12.3.1
### Patch Changes
- [#4670](https://github.com/xyflow/xyflow/pull/4670) [`b056564c`](https://github.com/xyflow/xyflow/commit/b056564c9658bb43b882eebfad5a7e224717ffb5) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix initial `fitView` not working correctly for `nodeOrigin` other than [0,0]
- [#4670](https://github.com/xyflow/xyflow/pull/4670) [`b056564c`](https://github.com/xyflow/xyflow/commit/b056564c9658bb43b882eebfad5a7e224717ffb5) Thanks [@peterkogo](https://github.com/peterkogo)! - Improve `fitView` to respect clamped node positions based on `nodeExtent`
- [#4653](https://github.com/xyflow/xyflow/pull/4653) [`02390f99`](https://github.com/xyflow/xyflow/commit/02390f9966d51c80e4e1b488733b5bf7322ad710) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Calculate viewport dimensions in `fitView` instead of using stored dimensions. Fixes [#4652](https://github.com/xyflow/xyflow/issues/4652)
- Updated dependencies [[`99ba64ac`](https://github.com/xyflow/xyflow/commit/99ba64ac2e1ce9c5ac3cab85a3d574edc0ecf4cc), [`b056564c`](https://github.com/xyflow/xyflow/commit/b056564c9658bb43b882eebfad5a7e224717ffb5)]:
- @xyflow/system@0.0.43
## 12.3.0 ## 12.3.0
### Minor Changes ### Minor Changes
+14 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xyflow/react", "name": "@xyflow/react",
"version": "12.3.0", "version": "12.6.4",
"description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
"keywords": [ "keywords": [
"react", "react",
@@ -28,10 +28,19 @@
"module": "dist/esm/index.js", "module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts", "types": "dist/esm/index.d.ts",
"exports": { "exports": {
"./package.json": "./package.json",
".": { ".": {
"types": "./dist/esm/index.d.ts", "node": {
"import": "./dist/esm/index.mjs", "types": "./dist/esm/index.d.ts",
"require": "./dist/umd/index.js" "module": "./dist/esm/index.js",
"require": "./dist/umd/index.js",
"import": "./dist/esm/index.mjs"
},
"browser": {
"import": "./dist/esm/index.js",
"require": "./dist/umd/index.js"
},
"default": "./dist/esm/index.js"
}, },
"./dist/base.css": "./dist/base.css", "./dist/base.css": "./dist/base.css",
"./dist/style.css": "./dist/style.css" "./dist/style.css": "./dist/style.css"
@@ -76,7 +85,7 @@
"postcss-nested": "^6.0.0", "postcss-nested": "^6.0.0",
"postcss-rename": "^0.6.1", "postcss-rename": "^0.6.1",
"react": "^18.2.0", "react": "^18.2.0",
"typescript": "5.1.3" "typescript": "5.4.5"
}, },
"rollup": { "rollup": {
"globals": { "globals": {
@@ -90,4 +90,57 @@ function BackgroundComponent({
BackgroundComponent.displayName = 'Background'; BackgroundComponent.displayName = 'Background';
/**
* The `<Background />` component makes it convenient to render different types of backgrounds common in node-based UIs. It comes with three variants: lines, dots and cross.
*
* @example
*
* A simple example of how to use the Background component.
*
* ```tsx
* import { useState } from 'react';
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
*
* export default function Flow() {
* return (
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
* <Background color="#ccc" variant={BackgroundVariant.Dots} />
* </ReactFlow>
* );
* }
* ```
*
* @example
*
* In this example you can see how to combine multiple backgrounds
*
* ```tsx
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
* import '@xyflow/react/dist/style.css';
*
* export default function Flow() {
* return (
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
* <Background
* id="1"
* gap={10}
* color="#f1f1f1"
* variant={BackgroundVariant.Lines}
* />
* <Background
* id="2"
* gap={100}
* color="#ccc"
* variant={BackgroundVariant.Lines}
* />
* </ReactFlow>
* );
* }
* ```
*
* @remarks
*
* When combining multiple <Background /> components its important to give each of them a unique id prop!
*
*/
export const Background = memo(BackgroundComponent); export const Background = memo(BackgroundComponent);
@@ -1,34 +1,60 @@
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
/**
* The three variants are exported as an enum for convenience. You can either import
* the enum and use it like `BackgroundVariant.Lines` or you can use the raw string
* value directly.
* @public
*/
export enum BackgroundVariant { export enum BackgroundVariant {
Lines = 'lines', Lines = 'lines',
Dots = 'dots', Dots = 'dots',
Cross = 'cross', Cross = 'cross',
} }
/**
* @expand
*/
export type BackgroundProps = { export type BackgroundProps = {
/** When multiple backgrounds are present on the page, each one should have a unique id. */
id?: string; id?: string;
/** Color of the pattern */ /** Color of the pattern. */
color?: string; color?: string;
/** Color of the background */ /** Color of the background. */
bgColor?: string; bgColor?: string;
/** Class applied to the container */ /** Class applied to the container. */
className?: string; className?: string;
/** Class applied to the pattern */ /** Class applied to the pattern. */
patternClassName?: string; patternClassName?: string;
/** Gap between repetitions of the pattern */ /**
* The gap between patterns. Passing in a tuple allows you to control the x and y gap
* independently.
* @default 20
*/
gap?: number | [number, number]; gap?: number | [number, number];
/** Size of a single pattern element */ /**
* The radius of each dot or the size of each rectangle if `BackgroundVariant.Dots` or
* `BackgroundVariant.Cross` is used. This defaults to 1 or 6 respectively, or ignored if
* `BackgroundVariant.Lines` is used.
*/
size?: number; size?: number;
/** Offset of the pattern */ /**
* Offset of the pattern.
* @default 0
*/
offset?: number | [number, number]; offset?: number | [number, number];
/** Line width of the Line pattern */ /**
* The stroke thickness used when drawing the pattern.
* @default 1
*/
lineWidth?: number; lineWidth?: number;
/** Variant of the pattern /**
* Variant of the pattern.
* @default BackgroundVariant.Dots
* @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross * @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
* 'lines', 'dots', 'cross' * 'lines', 'dots', 'cross'
*/ */
variant?: BackgroundVariant; variant?: BackgroundVariant;
/** Style applied to the container */ /** Style applied to the container. */
style?: CSSProperties; style?: CSSProperties;
}; };
@@ -2,6 +2,29 @@ import cc from 'classcat';
import type { ControlButtonProps } from './types'; import type { ControlButtonProps } from './types';
/**
* You can add buttons to the control panel by using the `<ControlButton />` component
* and pass it as a child to the [`<Controls />`](/api-reference/components/controls) component.
*
* @public
* @example
*```jsx
*import { MagicWand } from '@radix-ui/react-icons'
*import { ReactFlow, Controls, ControlButton } from '@xyflow/react'
*
*export default function Flow() {
* return (
* <ReactFlow nodes={[...]} edges={[...]}>
* <Controls>
* <ControlButton onClick={() => alert('Something magical just happened. ✨')}>
* <MagicWand />
* </ControlButton>
* </Controls>
* </ReactFlow>
* )
*}
*```
*/
export function ControlButton({ children, className, ...rest }: ControlButtonProps) { export function ControlButton({ children, className, ...rest }: ControlButtonProps) {
return ( return (
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}> <button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
@@ -125,4 +125,25 @@ function ControlsComponent({
ControlsComponent.displayName = 'Controls'; ControlsComponent.displayName = 'Controls';
/**
* The `<Controls />` component renders a small panel that contains convenient
* buttons to zoom in, zoom out, fit the view, and lock the viewport.
*
* @public
* @example
*```tsx
*import { ReactFlow, Controls } from '@xyflow/react'
*
*export default function Flow() {
* return (
* <ReactFlow nodes={[...]} edges={[...]}>
* <Controls />
* </ReactFlow>
* )
*}
*```
*
* @remarks To extend or customise the controls, you can use the [`<ControlButton />`](/api-reference/components/control-button) component
*
*/
export const Controls = memo(ControlsComponent); export const Controls = memo(ControlsComponent);
@@ -3,24 +3,46 @@ import type { PanelPosition } from '@xyflow/system';
import type { FitViewOptions } from '../../types'; import type { FitViewOptions } from '../../types';
/**
* @expand
*/
export type ControlProps = { export type ControlProps = {
/** Show button for zoom in/out */ /**
* Whether or not to show the zoom in and zoom out buttons. These buttons will adjust the viewport
* zoom by a fixed amount each press.
* @default true
*/
showZoom?: boolean; showZoom?: boolean;
/** Show button for fit view */ /**
* Whether or not to show the fit view button. By default, this button will adjust the viewport so
* that all nodes are visible at once.
* @default true
*/
showFitView?: boolean; showFitView?: boolean;
/** Show button for toggling interactivity */ /**
* Show button for toggling interactivity
* @default true
*/
showInteractive?: boolean; showInteractive?: boolean;
/** Options being used when fit view button is clicked */ /**
* Customise the options for the fit view button. These are the same options you would pass to the
* fitView function.
*/
fitViewOptions?: FitViewOptions; fitViewOptions?: FitViewOptions;
/** Callback when zoom in button is clicked */ /** Called in addition the default zoom behavior when the zoom in button is clicked. */
onZoomIn?: () => void; onZoomIn?: () => void;
/** Callback when zoom out button is clicked */ /** Called in addition the default zoom behavior when the zoom out button is clicked. */
onZoomOut?: () => void; onZoomOut?: () => void;
/** Callback when fit view button is clicked */ /**
* Called when the fit view button is clicked. When this is not provided, the viewport will be
* adjusted so that all nodes are visible.
*/
onFitView?: () => void; onFitView?: () => void;
/** Callback when interactivity is toggled */ /** Called when the interactive (lock) button is clicked. */
onInteractiveChange?: (interactiveStatus: boolean) => void; onInteractiveChange?: (interactiveStatus: boolean) => void;
/** Position of the controls on the pane /**
* Position of the controls on the pane
* @default PanelPosition.BottomLeft
* @example PanelPosition.TopLeft, PanelPosition.TopRight, * @example PanelPosition.TopLeft, PanelPosition.TopRight,
* PanelPosition.BottomLeft, PanelPosition.BottomRight * PanelPosition.BottomLeft, PanelPosition.BottomRight
*/ */
@@ -28,10 +50,19 @@ export type ControlProps = {
children?: ReactNode; children?: ReactNode;
/** Style applied to container */ /** Style applied to container */
style?: React.CSSProperties; style?: React.CSSProperties;
/** ClassName applied to container */ /** Class name applied to container */
className?: string; className?: string;
/**
* @default 'React Flow controls'
*/
'aria-label'?: string; 'aria-label'?: string;
/**
* @default 'vertical'
*/
orientation?: 'horizontal' | 'vertical'; orientation?: 'horizontal' | 'vertical';
}; };
/**
* @expand
*/
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>; export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
@@ -15,6 +15,8 @@ import type { MiniMapProps } from './types';
const defaultWidth = 200; const defaultWidth = 200;
const defaultHeight = 150; const defaultHeight = 150;
const filterHidden = (node: Node) => !node.hidden;
const selector = (s: ReactFlowState) => { const selector = (s: ReactFlowState) => {
const viewBB: Rect = { const viewBB: Rect = {
x: -s.transform[0] / s.transform[2], x: -s.transform[0] / s.transform[2],
@@ -25,7 +27,10 @@ const selector = (s: ReactFlowState) => {
return { return {
viewBB, viewBB,
boundingRect: s.nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup), viewBB) : viewBB, boundingRect:
s.nodeLookup.size > 0
? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, { filter: filterHidden }), viewBB)
: viewBB,
rfId: s.rfId, rfId: s.rfId,
panZoom: s.panZoom, panZoom: s.panZoom,
translateExtent: s.translateExtent, translateExtent: s.translateExtent,
@@ -44,8 +49,10 @@ function MiniMapComponent<NodeType extends Node = Node>({
nodeClassName = '', nodeClassName = '',
nodeBorderRadius = 5, nodeBorderRadius = 5,
nodeStrokeWidth, nodeStrokeWidth,
// We need to rename the prop to be `CapitalCase` so that JSX will render it as /*
// a component properly. * We need to rename the prop to be `CapitalCase` so that JSX will render it as
* a component properly.
*/
nodeComponent, nodeComponent,
bgColor, bgColor,
maskColor, maskColor,
@@ -118,7 +125,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
const onSvgNodeClick = onNodeClick const onSvgNodeClick = onNodeClick
? useCallback((event: MouseEvent, nodeId: string) => { ? useCallback((event: MouseEvent, nodeId: string) => {
const node = store.getState().nodeLookup.get(nodeId)!; const node: NodeType = store.getState().nodeLookup.get(nodeId)!.internals.userNode;
onNodeClick(event, node); onNodeClick(event, node);
}, []) }, [])
: undefined; : undefined;
@@ -136,7 +143,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
typeof maskStrokeWidth === 'number' ? maskStrokeWidth * viewScale : undefined, typeof maskStrokeWidth === 'number' ? maskStrokeWidth * viewScale : undefined,
'--xy-minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined, '--xy-minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined,
'--xy-minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined, '--xy-minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined,
'--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined, '--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'number' ? nodeStrokeWidth : undefined,
} as CSSProperties } as CSSProperties
} }
className={cc(['react-flow__minimap', className])} className={cc(['react-flow__minimap', className])}
@@ -176,4 +183,24 @@ function MiniMapComponent<NodeType extends Node = Node>({
MiniMapComponent.displayName = 'MiniMap'; MiniMapComponent.displayName = 'MiniMap';
/**
* The `<MiniMap />` component can be used to render an overview of your flow. It
* renders each node as an SVG element and visualizes where the current viewport is
* in relation to the rest of the flow.
*
* @public
* @example
*
* ```jsx
*import { ReactFlow, MiniMap } from '@xyflow/react';
*
*export default function Flow() {
* return (
* <ReactFlow nodes={[...]]} edges={[...]]}>
* <MiniMap nodeStrokeWidth={3} />
* </ReactFlow>
* );
*}
*```
*/
export const MiniMap = memo(MiniMapComponent) as typeof MiniMapComponent; export const MiniMap = memo(MiniMapComponent) as typeof MiniMapComponent;
@@ -6,7 +6,7 @@ import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { MiniMapNode } from './MiniMapNode'; import { MiniMapNode } from './MiniMapNode';
import type { ReactFlowState, Node, InternalNode } from '../../types'; import type { ReactFlowState, Node } from '../../types';
import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types'; import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types';
declare const window: any; declare const window: any;
@@ -21,8 +21,10 @@ function MiniMapNodes<NodeType extends Node>({
nodeClassName = '', nodeClassName = '',
nodeBorderRadius = 5, nodeBorderRadius = 5,
nodeStrokeWidth, nodeStrokeWidth,
// We need to rename the prop to be `CapitalCase` so that JSX will render it as /*
// a component properly. * We need to rename the prop to be `CapitalCase` so that JSX will render it as
* a component properly.
*/
nodeComponent: NodeComponent = MiniMapNode, nodeComponent: NodeComponent = MiniMapNode,
onClick, onClick,
}: MiniMapNodesProps<NodeType>) { }: MiniMapNodesProps<NodeType>) {
@@ -36,11 +38,13 @@ function MiniMapNodes<NodeType extends Node>({
return ( return (
<> <>
{nodeIds.map((nodeId) => ( {nodeIds.map((nodeId) => (
// The split of responsibilities between MiniMapNodes and /*
// NodeComponentWrapper may appear weird. However, its designed to * The split of responsibilities between MiniMapNodes and
// minimize the cost of updates when individual nodes change. * NodeComponentWrapper may appear weird. However, its designed to
// * minimize the cost of updates when individual nodes change.
// For more details, see a similar commit in `NodeRenderer/index.tsx`. *
* For more details, see a similar commit in `NodeRenderer/index.tsx`.
*/
<NodeComponentWrapper<NodeType> <NodeComponentWrapper<NodeType>
key={nodeId} key={nodeId}
id={nodeId} id={nodeId}
@@ -80,8 +84,9 @@ function NodeComponentWrapperInner<NodeType extends Node>({
shapeRendering: string; shapeRendering: string;
}) { }) {
const { node, x, y, width, height } = useStore((s) => { const { node, x, y, width, height } = useStore((s) => {
const node = s.nodeLookup.get(id) as InternalNode<NodeType>; const { internals } = s.nodeLookup.get(id)!;
const { x, y } = node.internals.positionAbsolute; const node = internals.userNode as NodeType;
const { x, y } = internals.positionAbsolute;
const { width, height } = getNodeDimensions(node); const { width, height } = getNodeDimensions(node);
return { return {
@@ -6,47 +6,97 @@ import type { Node } from '../../types';
export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string; export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
/**
* @expand
*/
export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & { export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & {
/** Color of nodes on minimap */ /**
* Color of nodes on minimap.
* @default "#e2e2e2"
*/
nodeColor?: string | GetMiniMapNodeAttribute<NodeType>; nodeColor?: string | GetMiniMapNodeAttribute<NodeType>;
/** Stroke color of nodes on minimap */ /**
* Stroke color of nodes on minimap.
* @default "transparent"
*/
nodeStrokeColor?: string | GetMiniMapNodeAttribute<NodeType>; nodeStrokeColor?: string | GetMiniMapNodeAttribute<NodeType>;
/** ClassName applied to nodes on minimap */ /**
* Class name applied to nodes on minimap.
* @default ""
*/
nodeClassName?: string | GetMiniMapNodeAttribute<NodeType>; nodeClassName?: string | GetMiniMapNodeAttribute<NodeType>;
/** Border radius of nodes on minimap */ /**
* Border radius of nodes on minimap.
* @default 5
*/
nodeBorderRadius?: number; nodeBorderRadius?: number;
/** Stroke width of nodes on minimap */ /**
* Stroke width of nodes on minimap.
* @default 2
*/
nodeStrokeWidth?: number; nodeStrokeWidth?: number;
/** Component used to render nodes on minimap */ /**
* A custom component to render the nodes in the minimap. This component must render an SVG
* element!
*/
nodeComponent?: ComponentType<MiniMapNodeProps>; nodeComponent?: ComponentType<MiniMapNodeProps>;
/** Background color of minimap */ /** Background color of minimap. */
bgColor?: string; bgColor?: string;
/** Color of mask representing viewport */ /**
* The color of the mask that covers the portion of the minimap not currently visible in the
* viewport.
* @default "rgba(240, 240, 240, 0.6)"
*/
maskColor?: string; maskColor?: string;
/** Stroke color of mask representing viewport */ /**
* Stroke color of mask representing viewport.
* @default transparent
*/
maskStrokeColor?: string; maskStrokeColor?: string;
/** Stroke width of mask representing viewport */ /**
* Stroke width of mask representing viewport.
* @default 1
*/
maskStrokeWidth?: number; maskStrokeWidth?: number;
/** Position of minimap on pane /**
* Position of minimap on pane.
* @default PanelPosition.BottomRight
* @example PanelPosition.TopLeft, PanelPosition.TopRight, * @example PanelPosition.TopLeft, PanelPosition.TopRight,
* PanelPosition.BottomLeft, PanelPosition.BottomRight * PanelPosition.BottomLeft, PanelPosition.BottomRight
*/ */
position?: PanelPosition; position?: PanelPosition;
/** Callback caled when minimap is clicked*/ /** Callback called when minimap is clicked. */
onClick?: (event: MouseEvent, position: XYPosition) => void; onClick?: (event: MouseEvent, position: XYPosition) => void;
/** Callback called when node on minimap is clicked */ /** Callback called when node on minimap is clicked. */
onNodeClick?: (event: MouseEvent, node: NodeType) => void; onNodeClick?: (event: MouseEvent, node: NodeType) => void;
/** If true, viewport is pannable via mini map component */ /**
* Determines whether you can pan the viewport by dragging inside the minimap.
* @default false
*/
pannable?: boolean; pannable?: boolean;
/** If true, viewport is zoomable via mini map component */ /**
* Determines whether you can zoom the viewport by scrolling inside the minimap.
* @default false
*/
zoomable?: boolean; zoomable?: boolean;
/** The aria-label attribute */ /**
* There is no text inside the minimap for a screen reader to use as an accessible name, so it's
* important we provide one to make the minimap accessible. The default is sufficient, but you may
* want to replace it with something more relevant to your app or product.
* @default "React Flow mini map"
*/
ariaLabel?: string | null; ariaLabel?: string | null;
/** Invert direction when panning the minimap viewport */ /** Invert direction when panning the minimap viewport. */
inversePan?: boolean; inversePan?: boolean;
/** Step size for zooming in/out on minimap */ /**
* Step size for zooming in/out on minimap.
* @default 10
*/
zoomStep?: number; zoomStep?: number;
/** Offset the viewport on the minmap, acts like a padding */ /**
* Offset the viewport on the minimap, acts like a padding.
* @default 5
*/
offsetScale?: number; offsetScale?: number;
}; };
@@ -57,6 +107,12 @@ export type MiniMapNodes<NodeType extends Node = Node> = Pick<
onClick?: (event: MouseEvent, nodeId: string) => void; onClick?: (event: MouseEvent, nodeId: string) => void;
}; };
/**
* The props that are passed to the MiniMapNode component
*
* @public
* @expand
*/
export type MiniMapNodeProps = { export type MiniMapNodeProps = {
id: string; id: string;
x: number; x: number;
@@ -32,6 +32,7 @@ function ResizeControl({
maxWidth = Number.MAX_VALUE, maxWidth = Number.MAX_VALUE,
maxHeight = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE,
keepAspectRatio = false, keepAspectRatio = false,
resizeDirection,
shouldResize, shouldResize,
onResizeStart, onResizeStart,
onResize, onResize,
@@ -56,13 +57,14 @@ function ResizeControl({
domNode: resizeControlRef.current, domNode: resizeControlRef.current,
nodeId: id, nodeId: id,
getStoreItems: () => { getStoreItems: () => {
const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = store.getState(); const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, domNode } = store.getState();
return { return {
nodeLookup, nodeLookup,
transform, transform,
snapGrid, snapGrid,
snapToGrid, snapToGrid,
nodeOrigin, nodeOrigin,
paneDomNode: domNode,
}; };
}, },
onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => {
@@ -73,8 +75,8 @@ function ResizeControl({
if (node && node.expandParent && node.parentId) { if (node && node.expandParent && node.parentId) {
const origin = node.origin ?? nodeOrigin; const origin = node.origin ?? nodeOrigin;
const width = change.width ?? node.measured.width!; const width = change.width ?? node.measured.width ?? 0;
const height = change.height ?? node.measured.height!; const height = change.height ?? node.measured.height ?? 0;
const child: ParentExpandChild = { const child: ParentExpandChild = {
id: node.id, id: node.id,
@@ -98,8 +100,10 @@ function ResizeControl({
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin); const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
// when the parent was expanded by the child node, its position will be clamped at /*
// 0,0 when node origin is 0,0 and to width, height if it's 1,1 * when the parent was expanded by the child node, its position will be clamped at
* 0,0 when node origin is 0,0 and to width, height if it's 1,1
*/
nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined; nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined; nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
} }
@@ -114,11 +118,12 @@ function ResizeControl({
} }
if (change.width !== undefined && change.height !== undefined) { if (change.width !== undefined && change.height !== undefined) {
const setAttributes = !resizeDirection ? true : resizeDirection === 'horizontal' ? 'width' : 'height';
const dimensionChange: NodeDimensionChange = { const dimensionChange: NodeDimensionChange = {
id, id,
type: 'dimensions', type: 'dimensions',
resizing: true, resizing: true,
setAttributes: true, setAttributes,
dimensions: { dimensions: {
width: change.width, width: change.width,
height: change.height, height: change.height,
@@ -139,11 +144,15 @@ function ResizeControl({
triggerNodeChanges(changes); triggerNodeChanges(changes);
}, },
onEnd: () => { onEnd: ({ width, height }) => {
const dimensionChange: NodeDimensionChange = { const dimensionChange: NodeDimensionChange = {
id: id, id: id,
type: 'dimensions', type: 'dimensions',
resizing: false, resizing: false,
dimensions: {
width,
height,
},
}; };
store.getState().triggerNodeChanges([dimensionChange]); store.getState().triggerNodeChanges([dimensionChange]);
}, },
@@ -159,6 +168,7 @@ function ResizeControl({
maxHeight, maxHeight,
}, },
keepAspectRatio, keepAspectRatio,
resizeDirection,
onResizeStart, onResizeStart,
onResize, onResize,
onResizeEnd, onResizeEnd,
@@ -200,4 +210,9 @@ export function ResizeControlLine(props: ResizeControlLineProps) {
return <ResizeControl {...props} variant={ResizeControlVariant.Line} />; return <ResizeControl {...props} variant={ResizeControlVariant.Line} />;
} }
/**
* To create your own resizing UI, you can use the `NodeResizeControl` component where you can pass children (such as icons).
* @public
*
*/
export const NodeResizeControl = memo(ResizeControl); export const NodeResizeControl = memo(ResizeControl);

Some files were not shown because too many files have changed in this diff Show More