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'
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.45.0-jammy
image: mcr.microsoft.com/playwright:v1.51.1-jammy
steps:
- name: Checkout repo
uses: actions/checkout@v4
+1 -1
View File
@@ -25,5 +25,5 @@ jobs:
title: Release packages
commit: 'chore(packages): bump'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASE_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:
**🐛 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!
**✏️ 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
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.
+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)
- 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)
- Peter • [Github](https://github.com/peterkogo)
+1
View File
@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
+2 -1
View File
@@ -7,6 +7,7 @@
"dev": "vite --port 3000 --open --host",
"serve": "vite serve --port 3000",
"build": "vite build",
"preview": "vite preview",
"test:dev": "cypress open",
"test": "pnpm test-component && pnpm test-e2e",
"test-component": "cypress run --component",
@@ -37,7 +38,7 @@
"cypress": "13.6.6",
"cypress-real-events": "1.12.0",
"start-server-and-test": "^2.0.2",
"typescript": "5.2.2",
"typescript": "5.4.5",
"vite": "4.5.0"
}
}
+10 -4
View File
@@ -1,5 +1,6 @@
import Basic from '../examples/Basic';
import Backgrounds from '../examples/Backgrounds';
import BrokenNodes from '../examples/BrokenNodes';
import ColorMode from '../examples/ColorMode';
import ClickDistance from '../examples/ClickDistance';
import ControlledUncontrolled from '../examples/ControlledUncontrolled';
@@ -49,7 +50,7 @@ import NodeToolbar from '../examples/NodeToolbar';
import UseConnection from '../examples/UseConnection';
import UseNodesInitialized from '../examples/UseNodesInit';
import UseNodesData from '../examples/UseNodesData';
import UseHandleConnections from '../examples/UseHandleConnections';
import UseNodeConnections from '../examples/UseNodeConnections';
import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop';
import DevTools from '../examples/DevTools';
import Redux from '../examples/Redux';
@@ -77,6 +78,11 @@ const routes: IRoute[] = [
path: 'backgrounds',
component: Backgrounds,
},
{
name: 'Broken Nodes',
path: 'broken-nodes',
component: BrokenNodes,
},
{
name: 'Color Mode',
path: 'color-mode',
@@ -313,9 +319,9 @@ const routes: IRoute[] = [
component: UseReactFlow,
},
{
name: 'useHandleConnections',
path: 'usehandleconnections',
component: UseHandleConnections,
name: 'useNodeConnections',
path: 'usenodeconnections',
component: UseNodeConnections,
},
{
name: 'useNodesData',
@@ -47,7 +47,7 @@ const AddNodeOnEdgeDrop = () => {
(event) => {
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) {
// we need to remove the wrapper bounds, in order to get the correct position
+20 -2
View File
@@ -11,6 +11,7 @@ import {
useReactFlow,
Panel,
OnNodeDrag,
FitViewOptions,
} from '@xyflow/react';
const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
@@ -54,10 +55,23 @@ const initialEdges: Edge[] = [
];
const defaultEdgeOptions = {};
const fitViewOptions: FitViewOptions = {
padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 },
};
const BasicFlow = () => {
const { addNodes, setNodes, getNodes, setEdges, getEdges, deleteElements, updateNodeData, toObject, setViewport } =
useReactFlow();
const {
addNodes,
setNodes,
getNodes,
setEdges,
getEdges,
deleteElements,
updateNodeData,
toObject,
setViewport,
fitView,
} = useReactFlow();
const updatePos = () => {
setNodes((nodes) =>
@@ -104,6 +118,7 @@ const BasicFlow = () => {
]);
setEdges([{ id: 'a-b', source: 'a', target: 'b' }]);
fitView();
};
const onUpdateNode = () => {
@@ -117,12 +132,14 @@ const BasicFlow = () => {
position: { x: Math.random() * 300, y: Math.random() * 300 },
className: 'light',
});
fitView();
};
return (
<ReactFlow
defaultNodes={initialNodes}
defaultEdges={initialEdges}
onNodesChange={console.log}
onNodeClick={onNodeClick}
onNodeDragStop={onNodeDragStop}
onNodeDragStart={onNodeDragStart}
@@ -134,6 +151,7 @@ const BasicFlow = () => {
minZoom={0.2}
maxZoom={4}
fitView
fitViewOptions={fitViewOptions}
defaultEdgeOptions={defaultEdgeOptions}
selectNodesOnDrag={false}
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 CustomEdge2 from './CustomEdge2';
import CustomEdge3 from './CustomEdge3';
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
@@ -63,6 +64,12 @@ const initialNodes: Node[] = [
data: { label: 'Output 9' },
position: { x: 675, y: 500 },
},
{
id: '10',
type: 'output',
data: { label: 'Output 10' },
position: { x: 50, y: 400 },
},
];
const initialEdges: Edge[] = [
@@ -137,6 +144,13 @@ const initialEdges: Edge[] = [
type: 'custom2',
data: { text: 'custom edge 2' },
},
{
id: 'e3a-10',
source: '3a',
target: '10',
type: 'custom3',
data: { text: 'custom edge 3' },
},
{
id: 'e5-6',
source: '5',
@@ -173,6 +187,7 @@ const initialEdges: Edge[] = [
const edgeTypes: EdgeTypes = {
custom: CustomEdge,
custom2: CustomEdge2,
custom3: CustomEdge3,
};
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 VerticalResizer from './VerticalResizer';
import HorizontalResizer from './HorizontalResizer';
import BottomRightResizer from './BottomRightResizer';
const nodeTypes = {
defaultResizer: DefaultResizer,
customResizer: CustomResizer,
verticalResizer: VerticalResizer,
horizontalResizer: HorizontalResizer,
bottomRightResizer: BottomRightResizer,
};
const nodeStyle = {
@@ -166,6 +168,13 @@ const initialNodes: Node[] = [
expandParent: true,
style: { ...nodeStyle },
},
{
id: '6',
type: 'bottomRightResizer',
data: { label: 'Bottom Right with horizontal direction' },
position: { x: 500, y: 500 },
style: { ...nodeStyle },
},
];
const CustomNodeFlow = () => {
+13 -3
View File
@@ -12,6 +12,8 @@ import {
Controls,
Background,
Panel,
ReactFlowProvider,
useReactFlow,
} from '@xyflow/react';
import { getNodesAndEdges } from './utils';
@@ -22,6 +24,7 @@ const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(25, 25);
const StressFlow = () => {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const { fitView } = useReactFlow();
const onConnect = useCallback((connection: Connection) => {
setEdges((eds) => addEdge(connection, eds));
}, []);
@@ -191,12 +194,13 @@ const StressFlow = () => {
return {
...n,
position: {
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
x: Math.random() * window.innerWidth * 4,
y: Math.random() * window.innerHeight * 4,
},
};
});
});
fitView();
};
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 { 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 }) {
const onConnect = useCallback(
@@ -11,9 +11,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
(connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections),
[nodeId]
);
const connections = useHandleConnections({
type: handleProps.type,
id: handleProps.id,
const connections = useNodeConnections({
handleType: handleProps.type,
handleId: handleProps.id,
onConnect,
onDisconnect,
});
@@ -28,10 +28,11 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
const CustomNode: FC<NodeProps> = ({ id }) => {
return (
<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>
<CustomHandle nodeId={id} type="source" position={Position.Right} id="a" 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="s1" style={{ top: 10 }} />
<CustomHandle nodeId={id} type="source" position={Position.Right} id="s2" style={{ top: 20 }} />
</div>
);
};
@@ -1,5 +1,5 @@
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 }) {
const onConnect = useCallback(
@@ -14,9 +14,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
},
[nodeId]
);
const connections = useHandleConnections({
type: handleProps.type,
id: handleProps.id,
const connections = useNodeConnections({
handleType: handleProps.type,
handleId: handleProps.id,
onConnect,
onDisconnect,
});
@@ -69,17 +69,16 @@ const initEdges = [
source: '1',
target: '3',
},
{
id: 'e4a-5',
source: '4',
sourceHandle: 'a',
sourceHandle: 's1',
target: '5',
},
{
id: 'e4b-5',
source: '4',
sourceHandle: 'b',
sourceHandle: 's2',
target: '6',
},
];
@@ -92,6 +91,8 @@ const CustomNodeFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
console.log(edges);
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
return (
@@ -1,10 +1,10 @@
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 '.';
function ResultNode() {
const connections = useHandleConnections({
type: 'target',
const connections = useNodeConnections({
handleType: 'target',
});
const nodesData = useNodesData<MyNode>(connections.map((connection) => connection.source));
const textNodes = nodesData.filter(isTextNode);
@@ -1,11 +1,11 @@
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 '.';
function UppercaseNode({ id }: NodeProps) {
const { updateNodeData } = useReactFlow();
const connections = useHandleConnections({
type: 'target',
const connections = useNodeConnections({
handleType: 'target',
});
const nodesData = useNodesData<MyNode>(connections[0]?.source);
const textNode = isTextNode(nodesData) ? nodesData : null;
@@ -21,7 +21,7 @@ export type UppercaseNode = Node<{ text: string }, 'uppercase'>;
export type MyNode = TextNode | ResultNode | 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 = {
@@ -10,7 +10,7 @@ import {
useEdgesState,
useOnSelectionChange,
OnSelectionChangeParams,
OnSelectionChangeFunc,
Panel,
} from '@xyflow/react';
const initialNodes: Node[] = [
@@ -52,6 +52,12 @@ const Flow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
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 (
<ReactFlow
@@ -60,27 +66,17 @@ const Flow = () => {
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
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" />
{secondLoggerActive && <SelectionLogger id="Logger 2" />}
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
<button onClick={toggleSecondLogger}>{secondLoggerActive ? 'Disable' : 'Enable'} Logger 2</button>
</div>
</ReactFlowProvider>
</ReactFlow>
);
};
export default WrappedFlow;
export default Flow;
@@ -170,7 +170,6 @@ const UseZoomPanHelperFlow = () => {
onConnect={onConnect}
onPaneClick={onPaneClick}
fitView
fitViewOptions={{ duration: 1200, padding: 0.2 }}
maxZoom={Infinity}
>
<Panel position="top-right">
@@ -183,7 +182,16 @@ const UseZoomPanHelperFlow = () => {
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 onClick={onAddNode}>add node</button>
<button onClick={onResetNodes}>reset nodes</button>
@@ -4,6 +4,7 @@ export default {
flowProps: {
fitView: true,
multiSelectionKeyCode: 's',
deleteKeyCode: 'd',
nodes: [
{
id: '1',
@@ -3,6 +3,8 @@ import DragHandleNode from './components/DragHandleNode';
export default {
flowProps: {
fitView: true,
deleteKeyCode: 'd',
multiSelectionKeyCode: 's',
nodeTypes: {
DragHandleNode,
},
+16 -16
View File
@@ -12,25 +12,25 @@
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.2.1",
"@sveltejs/kit": "^2.5.10",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-svelte": "^2.35.0",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"svelte": "^4.2.12",
"svelte-check": "^3.6.6",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^5.2.12"
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.20.7",
"@typescript-eslint/eslint-plugin": "^8.30.1",
"@typescript-eslint/parser": "^8.30.1",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-svelte": "^3.5.1",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.27.0",
"svelte-check": "^4.1.6",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"vite": "^6.3.0"
},
"type": "module",
"dependencies": {
"@dagrejs/dagre": "^1.0.4",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@dagrejs/dagre": "^1.1.4",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@xyflow/svelte": "workspace:^"
}
}
@@ -35,7 +35,7 @@
<header>
<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}
<option value={`/examples/${route}`}>{route}</option>
{/each}
@@ -3,6 +3,8 @@ import { MarkerType } from '@xyflow/svelte';
export default {
flowProps: {
fitView: true,
multiSelectionKey: ['Meta', 's'],
deleteKey: 'd',
nodes: [
{
id: '1',
@@ -1,9 +1,18 @@
<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;
export let data: $$Props['data'];
let {
data
}: NodeProps<
Node<{ label: string; toolbarPosition: Position; toolbarAlign: Align; toolbarVisible: boolean }>
> = $props();
</script>
<NodeToolbar
@@ -1,11 +1,11 @@
<script lang="ts">
import type { NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
let {}: NodeProps = $props();
</script>
<div class="container">
<div class="drag-handle custom-drag-handle" />
<div class="drag-handle custom-drag-handle"></div>
</div>
<style>
@@ -4,6 +4,9 @@ export default {
flowProps: {
fitView: true,
nodeDragThreshold: 0,
autoPanOnConnect: false,
autoPanOnNodeDrag: false,
deleteKey: 'd',
nodeTypes: {
DragHandleNode
},
+2
View File
@@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = false;
@@ -1,11 +1,16 @@
<script lang="ts">
import { Header } from '$components/Header';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script>
<div class="app">
<Header />
<div class="flow">
<slot />
{@render children?.()}
</div>
</div>
@@ -1,7 +1,7 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
useNodeConnections,
useSvelteFlow,
type Edge,
type Node,
@@ -19,21 +19,27 @@
}
];
const nodes = writable<Node[]>(initialNodes);
const edges = writable<Edge[]>([]);
let nodes = $state.raw<Node[]>(initialNodes);
let edges = $state.raw<Edge[]>([]);
let connectingNodeId: string | null = '0';
let rect: DOMRectReadOnly;
let connectingNodeId: string | null = $state('0');
let rect = $state<DOMRectReadOnly>();
let id = 1;
const getId = () => `${id++}`;
const { screenToFlowPosition, flowToScreenPosition } = useSvelteFlow();
const connections = useNodeConnections({ id: '0', handleType: 'source' });
$inspect(connections.current);
const handleConnectEnd: OnConnectEnd = (event) => {
if (!connectingNodeId) return;
// 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) {
const id = getId();
const position = {
@@ -56,15 +62,14 @@
origin: [0.5, 0.0]
};
$nodes.push(newNode);
$edges.push({
nodes = [...nodes, newNode];
const newEdge = {
source: connectingNodeId,
target: id,
id: `${connectingNodeId}--${id}`
});
$nodes = $nodes;
$edges = $edges;
};
edges = [...edges, newEdge];
}
};
</script>
@@ -73,8 +78,8 @@
<div class="wrapper" bind:contentRect={rect}>
<SvelteFlow
{nodes}
{edges}
bind:nodes
bind:edges
fitView
fitViewOptions={{ padding: 2 }}
onconnectstart={(_, { nodeId }) => {
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -18,7 +17,7 @@
targetPosition: Position.Left
};
const nodes = writable([
let nodes = $state.raw([
{
id: 'A',
position: { x: 0, y: 150 },
@@ -30,16 +29,16 @@
{ 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-C', source: 'A', target: 'C' },
{ id: 'A-D', source: 'A', target: 'D' }
]);
let colorMode: ColorMode = 'light';
let colorMode: ColorMode = $state('light');
</script>
<SvelteFlow {nodes} {edges} {colorMode} fitView>
<SvelteFlow bind:nodes bind:edges {colorMode} colorModeSSR={'light'} fitView>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
@@ -1,7 +1,6 @@
<script lang="ts">
import { SvelteFlow } from '@xyflow/svelte';
import { Background, BackgroundVariant, type Edge, type Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
import CustomNode from './CustomNode.svelte';
import ConnectionLine from './ConnectionLine.svelte';
@@ -12,7 +11,7 @@
custom: CustomNode
};
const nodes = writable<Node[]>([
let nodes = $state.raw<Node[]>([
{
id: 'connectionline-1',
type: 'custom',
@@ -21,12 +20,11 @@
}
]);
const edges = writable<Edge[]>([]);
let edges = $state.raw<Edge[]>([]);
</script>
<div style="height:100vh;">
<SvelteFlow {nodeTypes} {nodes} {edges} fitView>
<ConnectionLine slot="connectionLine" />
<SvelteFlow bind:nodes bind:edges {nodeTypes} fitView connectionLineComponent={ConnectionLine}>
<Background variant={BackgroundVariant.Lines} />
</SvelteFlow>
</div>
@@ -3,22 +3,24 @@
const connection = useConnection();
let path: string | null = null;
$: if ($connection.inProgress) {
const { from, to, fromPosition, toPosition } = $connection;
const pathParams = {
sourceX: from.x,
sourceY: from.y,
sourcePosition: fromPosition,
targetX: to.x,
targetY: to.y,
targetPosition: toPosition
};
[path] = getBezierPath(pathParams);
}
let path: string | null = $derived.by(() => {
if (connection.current.inProgress) {
const { from, to, fromPosition, toPosition } = connection.current;
const pathParams = {
sourceX: from.x,
sourceY: from.y,
sourcePosition: fromPosition,
targetX: to.x,
targetY: to.y,
targetPosition: toPosition
};
const [path] = getBezierPath(pathParams);
return path;
}
return null;
});
</script>
{#if $connection.inProgress}
<path d={path} fill="none" stroke={$connection.fromHandle.id} />
{#if connection.current.inProgress}
<path d={path} fill="none" stroke={connection.current.fromHandle.id} />
{/if}
@@ -1,10 +1,7 @@
<script lang="ts">
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
export let isConnectable: $$Props['isConnectable'];
$$restProps;
let { isConnectable }: NodeProps = $props();
const handleStyle = 'width: 10px; height: 10px; bottom: -5px;';
</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">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -20,9 +28,7 @@
colorNode: CustomNode
};
const bgColor = writable('#1A192B');
const nodes = writable<Node[]>([
let nodes = $state.raw<Node[]>([
{
id: '1',
type: 'input',
@@ -33,7 +39,7 @@
{
id: '2',
type: 'colorNode',
data: { colorStore: bgColor },
data: {},
position: { x: 250, y: 50 }
},
{
@@ -52,7 +58,7 @@
}
]);
const edges = writable<Edge[]>([
let edges = $state.raw<Edge[]>([
{
id: 'e1-2',
source: '1',
@@ -81,10 +87,10 @@
</script>
<SvelteFlow
{nodes}
{edges}
bind:nodes
bind:edges
{nodeTypes}
style="--xy-background-color: {$bgColor}"
style="--xy-background-color: {bg.color}"
fitView
onconnect={onConnect}
>
@@ -1,12 +1,8 @@
<script lang="ts">
import type { Writable } from 'svelte/store';
import { Handle, Position, type NodeProps, type Node } from '@xyflow/svelte';
import { bg } from './+page.svelte';
type $$Props = NodeProps<Node<{ colorStore: Writable<string> }>>;
export let data: $$Props['data'];
$$restProps;
let { data }: NodeProps<Node> = $props();
const { colorStore } = data;
</script>
@@ -14,13 +10,13 @@
<div class="custom">
<Handle type="target" position={Position.Left} />
<div>
Custom Color Picker Node: <strong>{$colorStore}</strong>
Custom Color Picker Node: <strong>{bg.color}</strong>
</div>
<input
class="nodrag"
type="color"
on:input={(evt) => colorStore.set(evt.currentTarget.value)}
value={$colorStore}
oninput={(evt) => (bg.color = evt.currentTarget.value)}
value={bg.color}
/>
<Handle type="source" position={Position.Right} id="a" style="top: 20px;" />
<Handle type="source" position={Position.Right} id="b" style="top: auto; bottom: 10px;" />
@@ -1,79 +1,77 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Background, Position, ConnectionLineType, Panel } from '@xyflow/svelte';
import type { Edge, Node } from '@xyflow/svelte';
import dagre from '@dagrejs/dagre';
import { SvelteFlow, Background, Position, ConnectionLineType, Panel } from '@xyflow/svelte';
import type { Edge, Node } from '@xyflow/svelte';
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();
dagreGraph.setDefaultEdgeLabel(() => ({}));
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 172;
const nodeHeight = 36;
const nodeWidth = 172;
const nodeHeight = 36;
function getLayoutedElements(nodes: Node[], edges: Edge[], direction = 'TB') {
const isHorizontal = direction === 'LR';
dagreGraph.setGraph({ rankdir: direction });
function getLayoutedElements(nodes: Node[], edges: Edge[], direction = 'TB') {
const isHorizontal = direction === 'LR';
dagreGraph.setGraph({ rankdir: direction });
nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
});
nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
});
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
dagre.layout(dagreGraph);
dagre.layout(dagreGraph);
nodes.forEach((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
node.targetPosition = isHorizontal ? Position.Left : Position.Top;
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
const layoutedNodes = nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
node.position = {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2
};
});
return {
...node,
targetPosition: isHorizontal ? Position.Left : Position.Top,
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
position: {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2
}
};
});
return { nodes, edges };
}
return { nodes: layoutedNodes, edges };
}
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
initialNodes,
initialEdges
);
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
initialNodes,
initialEdges
);
const nodes = writable<Node[]>(layoutedNodes);
const edges = writable<Edge[]>(layoutedEdges);
let nodes = $state.raw<Node[]>(layoutedNodes);
let edges = $state.raw<Edge[]>(layoutedEdges);
function onLayout(direction: string) {
const layoutedElements = getLayoutedElements($nodes, $edges, direction);
function onLayout(direction: string) {
const layoutedElements = getLayoutedElements(nodes, edges, direction);
$nodes = layoutedElements.nodes;
$edges = layoutedElements.edges;
// nodes.set(layoutedElements.nodes);
// edges.set(layoutedElements.edges);
}
nodes = layoutedElements.nodes;
edges = layoutedElements.edges;
}
</script>
<div style="height:100vh;">
<SvelteFlow
{nodes}
{edges}
fitView
connectionLineType={ConnectionLineType.SmoothStep}
defaultEdgeOptions={{ type: 'smoothstep', animated: true }}
>
<Panel position="top-right">
<button on:click={() => onLayout('TB')}>vertical layout</button>
<button on:click={() => onLayout('LR')}>horizontal layout</button>
</Panel>
<Background />
</SvelteFlow>
<SvelteFlow
bind:nodes
bind:edges
fitView
connectionLineType={ConnectionLineType.SmoothStep}
defaultEdgeOptions={{ type: 'smoothstep', animated: true }}
>
<Panel position="top-right">
<button onclick={() => onLayout('TB')}>vertical layout</button>
<button onclick={() => onLayout('LR')}>horizontal layout</button>
</Panel>
<Background />
</SvelteFlow>
</div>
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -13,7 +12,7 @@
import '@xyflow/svelte/dist/style.css';
const nodes = writable<Node[]>([
const initialNodes = [
{
id: '1',
type: 'input',
@@ -32,9 +31,11 @@
data: { label: 'Output Node' },
position: { x: 300, y: 150 }
}
]);
];
const edges = writable([
let nodes = $state.raw<Node[]>(initialNodes);
let edges = $state.raw([
{
id: '1-2',
type: 'default',
@@ -50,7 +51,7 @@
}
]);
const svelteFlow = useSvelteFlow();
const { screenToFlowPosition } = useSvelteFlow();
const onDragOver = (event: DragEvent) => {
event.preventDefault();
@@ -68,7 +69,7 @@
}
const type = event.dataTransfer.getData('application/svelteflow') || 'default';
const position = svelteFlow.screenToFlowPosition({
const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY
});
@@ -79,17 +80,14 @@
data: { label: `${type} node` }
};
$nodes.push(newNode);
$nodes = $nodes;
nodes = [...nodes, newNode];
};
$: {
console.log($nodes);
}
$inspect(nodes);
</script>
<main>
<SvelteFlow {nodes} {edges} fitView on:dragover={onDragOver} on:drop={onDrop}>
<SvelteFlow bind:nodes bind:edges fitView ondragover={onDragOver} ondrop={onDrop}>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
@@ -1,6 +1,4 @@
<script lang="ts">
import { useSvelteFlow } from '@xyflow/svelte';
const onDragStart = (event: DragEvent, nodeType: string) => {
if (!event.dataTransfer) {
return null;
@@ -9,29 +7,28 @@
event.dataTransfer.setData('application/svelteflow', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
const { zoomIn, zoomOut, fitView, viewport, nodes } = useSvelteFlow();
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<aside>
<div class="label">You can drag these nodes to the pane on the left.</div>
<div
class="input-node node"
on:dragstart={(event) => onDragStart(event, 'input')}
ondragstart={(event) => onDragStart(event, 'input')}
draggable={true}
>
Input Node
</div>
<div
class="default-node node"
on:dragstart={(event) => onDragStart(event, 'default')}
ondragstart={(event) => onDragStart(event, 'default')}
draggable={true}
>
Default Node
</div>
<div
class="output-node node"
on:dragstart={(event) => onDragStart(event, 'output')}
ondragstart={(event) => onDragStart(event, 'output')}
draggable={true}
>
Output Node
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -7,14 +6,15 @@
BackgroundVariant,
MiniMap,
MarkerType,
type Connection
type Connection,
type EdgeTypes
} from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
import ButtonEdge from './ButtonEdge.svelte';
import CustomBezierEdge from './CustomBezierEdge.svelte';
const nodes = writable([
let nodes = $state.raw([
{
id: '1',
type: 'input',
@@ -54,7 +54,7 @@
}
]);
const edges = writable([
let edges = $state.raw([
{
id: 'e1-2',
source: '1',
@@ -128,45 +128,29 @@
target: '6',
label: 'hi',
labelStyle: 'background: red; font-weight: 700; padding: 5px;',
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
}
style: 'stroke: #ffcc0'
}
]);
const edgeTypes = {
const edgeTypes: EdgeTypes = {
button: ButtonEdge,
customBezier: CustomBezierEdge
};
function getEdgeId(connection: Connection) {
function getEdgeId(connection: Connection) {
return `edge-${connection.source}-${connection.target}}`;
}
$: console.log('edges', $edges);
$inspect(edges);
</script>
<SvelteFlow
{nodes}
{edges}
{edgeTypes}
bind:nodes
bind:edges
{edgeTypes}
fitView
nodeDragThreshold={2}
onedgecreate={(connection) => {
onbeforeconnect={(connection) => {
console.log('on edge create', connection);
return {
@@ -1,59 +1,80 @@
<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;
export let id: $$Props['id'] = '';
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({
let {
style,
markerEnd,
sourceX,
sourceY,
sourcePosition,
targetX,
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>
<BaseEdge path={edgePath} {markerEnd} {style} />
<EdgeLabelRenderer>
<div
class="edgeButtonContainer nodrag nopan"
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
>
{#if !reconnecting}
<BaseEdge path={edgePath} {markerEnd} {style} />
<EdgeLabel x={labelX} y={labelY}>
<button
class="edgeButton"
on:click={(event) => {
onclick={(event) => {
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>
</div>
</EdgeLabelRenderer>
</EdgeLabel>
{/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>
.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 {
width: 20px;
height: 20px;
@@ -61,8 +82,7 @@
border: 1px solid #fff;
cursor: pointer;
border-radius: 50%;
font-size: 12px;
line-height: 1;
font-size: 10px;
}
.edgeButton:hover {
@@ -1,38 +1,7 @@
<script lang="ts">
import { BezierEdge, type EdgeProps } from '@xyflow/svelte';
type $$Props = EdgeProps;
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;
let { ...props }: EdgeProps = $props();
</script>
<BezierEdge
{sourceX}
{sourceY}
{sourcePosition}
{targetX}
{targetY}
{targetPosition}
{label}
{labelStyle}
{markerStart}
{markerEnd}
{style}
{interactionWidth}
pathOptions={{ curvature: 1 }}
/>
<BezierEdge {...props} pathOptions={{ curvature: 1 }} />
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -17,11 +16,11 @@
const panOnDrag = [1, 2];
const onMoveStart = (e: any) => console.log('move start', e);
const onMove = (e: any) => console.log('move', e);
const onMoveEnd = (e: any) => console.log('move end', e);
const onmovestart = (e: any) => console.log('move start', e);
const onmove = (e: any) => console.log('move', e);
const onmoveend = (e: any) => console.log('move end', e);
const nodes = writable([
let nodes = $state.raw([
{
id: '1',
type: 'input',
@@ -34,23 +33,23 @@
{ 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-3', source: '1', target: '3' }
]);
</script>
<SvelteFlow
{nodes}
{edges}
bind:nodes
bind:edges
fitView
selectionMode={SelectionMode.Partial}
selectionOnDrag
panOnScroll
{panOnDrag}
{onMoveStart}
{onMove}
{onMoveEnd}
{onmovestart}
{onmove}
{onmoveend}
>
<Controls />
<Background variant={BackgroundVariant.Dots} />
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -20,7 +19,7 @@
multi: MultiHandleNode
};
const nodes = writable<Node[]>([
let nodes = $state.raw<Node[]>([
{
id: '1',
type: 'single',
@@ -60,7 +59,7 @@
}
]);
const edges = writable<Edge[]>([
let edges = $state.raw<Edge[]>([
{
id: 'e1-2',
source: '1',
@@ -87,7 +86,7 @@
]);
</script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView colorMode="dark">
<SvelteFlow bind:nodes bind:edges {nodeTypes} fitView colorMode="dark">
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
@@ -4,12 +4,10 @@
Position,
type NodeProps,
type Connection,
useHandleConnections
useNodeConnections
} from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
let { id }: NodeProps = $props();
function onConnectTarget(connection: Connection[]) {
console.log('connect target', connection);
@@ -27,13 +25,18 @@
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);
}
});
$: {
console.log('connections', id, $connections);
}
$$restProps;
$inspect(connections.current);
</script>
<div class="custom">
@@ -1,9 +1,7 @@
<script lang="ts">
import { Handle, Position, type NodeProps, type Connection } from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
let { id }: NodeProps = $props();
function onConnectTarget(connection: Connection[]) {
console.log('connect target', connection);
@@ -20,8 +18,6 @@
function onDisconnectSource(connection: Connection[]) {
console.log('disconnect source', connection);
}
$$restProps;
</script>
<div class="custom">
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -13,7 +12,7 @@
import '@xyflow/svelte/dist/style.css';
const nodes = writable([
let nodes = $state.raw([
{
id: '1',
type: 'input',
@@ -25,37 +24,37 @@
{ 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-3', source: '1', target: '3' }
]);
const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node);
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
const onPaneClick = (event: MouseEvent) => console.log('onPaneClick', event);
const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event);
const onPaneContextMenu = (event: MouseEvent) => console.log('onPaneContextMenu', event);
const onMoveEnd: OnMoveEnd = (_, viewport) => console.log('onMoveEnd', viewport);
const onnodedragstart = (_: MouseEvent, node: Node) => console.log('drag start', node);
const onnodedragstop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onnodeclick = (_: MouseEvent, node: Node) => console.log('click', node);
const onedgeclick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
const onpaneclick = (event: MouseEvent) => console.log('onPaneClick', event);
const onpanescroll = (event?: WheelEvent) => console.log('onPaneScroll', event);
const onpanecontextmenu = (event: MouseEvent) => console.log('onPaneContextMenu', event);
const onmoveend: OnMoveEnd = (_, viewport) => console.log('onMoveEnd', viewport);
let isSelectable = false;
let isDraggable = false;
let isConnectable = false;
let zoomOnScroll = false;
let zoomOnPinch = false;
let panOnScroll = false;
let panOnScrollMode = PanOnScrollMode.Free;
let zoomOnDoubleClick = false;
let panOnDrag = true;
let captureZoomClick = false;
let captureZoomScroll = false;
let captureElementClick = false;
let isSelectable = $state(false);
let isDraggable = $state(false);
let isConnectable = $state(false);
let zoomOnScroll = $state(false);
let zoomOnPinch = $state(false);
let panOnScroll = $state(false);
let panOnScrollMode = $state(PanOnScrollMode.Free);
let zoomOnDoubleClick = $state(false);
let panOnDrag = $state(true);
let captureZoomClick = $state(false);
let captureZoomScroll = $state(false);
let captureElementClick = $state(false);
</script>
<SvelteFlow
{nodes}
{edges}
bind:nodes
bind:edges
elementsSelectable={isSelectable}
nodesConnectable={isConnectable}
nodesDraggable={isDraggable}
@@ -65,7 +64,8 @@
{panOnScrollMode}
{zoomOnDoubleClick}
{panOnDrag}
{onMoveEnd}
{onmoveend}
fitView
>
<MiniMap />
<Controls />
@@ -143,7 +143,7 @@
<select
id="panonscrollmode"
bind:value={panOnScrollMode}
on:change={(event) => {
onchange={(event) => {
panOnScrollMode = PanOnScrollMode.Free;
}}
class="svelte-flow__panonscrollmode"
@@ -1,31 +1,34 @@
<script lang="ts">
import { SvelteFlow, Background, Controls, useSvelteFlow } from '@xyflow/svelte';
import type { Edge, Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
import '@xyflow/svelte/dist/style.css';
import { initialNodes, initialEdges } from './nodes-and-edges';
const nodes = writable<Node[]>(initialNodes);
const edges = writable<Edge[]>(initialEdges);
let nodes = $state.raw<Node[]>(initialNodes);
let edges = $state.raw<Edge[]>(initialEdges);
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>
<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 />
<Controls />
</SvelteFlow>
@@ -48,10 +51,10 @@
}
:global(
.intersection-flow .svelte-flow__node.selected,
.intersection-flow .svelte-flow__node:hover,
.intersection-flow .svelte-flow__node:focus
) {
.intersection-flow .svelte-flow__node.selected,
.intersection-flow .svelte-flow__node:hover,
.intersection-flow .svelte-flow__node:focus
) {
box-shadow: 6px 6px 0 1px rgba(0, 0, 0, 0.7);
background-color: #eee;
}
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Panel, type Edge } from '@xyflow/svelte';
import DefaultResizer from './DefaultResizer.svelte';
@@ -19,9 +18,9 @@
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',
type: 'defaultResizer',
@@ -128,12 +127,12 @@
}
]);
let snapToGrid = false;
let snapToGrid = $state(false);
</script>
<SvelteFlow
{nodes}
{edges}
bind:nodes
bind:edges
{nodeTypes}
minZoom={0.2}
maxZoom={5}
@@ -144,7 +143,7 @@
<Controls />
<Panel position="bottom-right">
<button
on:click={() => {
onclick={() => {
snapToGrid = !snapToGrid;
}}
>
@@ -1,10 +1,8 @@
<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';
type $$Props = NodeProps<ResizeNode>;
export let data: $$Props['data'];
let { data }: NodeProps<ResizeNode> = $props();
</script>
<NodeResizeControl
@@ -2,10 +2,7 @@
import { Handle, NodeResizer, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>;
export let data: $$Props['data'];
export let selected: $$Props['selected'] = undefined;
let { data, selected }: NodeProps<ResizeNode> = $props();
</script>
<NodeResizer
@@ -2,9 +2,7 @@
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>;
export let data: $$Props['data'];
let { data }: NodeProps<ResizeNode> = $props();
</script>
<NodeResizeControl
@@ -2,9 +2,7 @@
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types';
type $$Props = NodeProps<ResizeNode>;
export let data: $$Props['data'];
let { data }: NodeProps<ResizeNode> = $props();
</script>
<NodeResizeControl
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Background,
@@ -11,7 +10,7 @@
import '@xyflow/svelte/dist/style.css';
import CustomNode from './CustomNode.svelte';
import CustomNode, { type CustomNodeType } from './CustomNode.svelte';
import SelectedNodesToolbar from './SelectedNodesToolbar.svelte';
const nodeTypes: NodeTypes = {
@@ -19,9 +18,9 @@
};
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',
type: 'custom',
@@ -51,12 +50,12 @@
const initialEdges: Edge[] = [];
const nodes = writable<Node[]>(initialNodes);
const edges = writable<Edge[]>(initialEdges);
let nodes = $state.raw<Node[]>(initialNodes);
let edges = $state.raw<Edge[]>(initialEdges);
</script>
<div style="height: 100vh;">
<SvelteFlow {nodes} {edges} {nodeTypes} fitView>
<SvelteFlow bind:nodes bind:edges {nodeTypes} fitView>
<Background />
<SelectedNodesToolbar />
</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">
import { NodeToolbar, type NodeProps, Handle, Position } from '@xyflow/svelte';
type $$Props = NodeProps;
$$restProps;
export let data: $$Props['data'];
let { data }: NodeProps<CustomNodeType> = $props();
</script>
<NodeToolbar
@@ -1,10 +1,12 @@
<script lang="ts">
import { NodeToolbar, useNodes } from '@xyflow/svelte';
const nodes = useNodes();
let nodes = useNodes();
$: selectedNodeIds = $nodes.filter((node) => node.selected).map((node) => node.id);
$: isVisible = selectedNodeIds.length > 1;
let selectedNodeIds = $derived(
nodes.current.filter((node) => node.selected).map((node) => node.id)
);
let isVisible = $derived(selectedNodeIds.length > 1);
</script>
<NodeToolbar nodeId={selectedNodeIds} {isVisible}>
@@ -1,240 +1,8 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
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);
}
import { SvelteFlowProvider } from '@xyflow/svelte';
import Flow from './Flow.svelte';
</script>
<SvelteFlow
{nodes}
{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]}
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>
<SvelteFlowProvider>
<Flow />
</SvelteFlowProvider>
@@ -1,46 +1,35 @@
<script lang="ts">
import {
BaseEdge,
EdgeLabelRenderer,
useEdges,
type EdgeProps,
getBezierPath
} from '@xyflow/svelte';
import { BaseEdge, useEdges, getBezierPath, type EdgeProps, EdgeLabel } from '@xyflow/svelte';
type $$Props = EdgeProps;
let { ...props }: EdgeProps = $props();
$: [path, labelX, labelY] = getBezierPath({
sourceX: $$props.sourceX,
sourceY: $$props.sourceY,
targetX: $$props.targetX,
targetY: $$props.targetY,
sourcePosition: $$props.sourcePosition,
targetPosition: $$props.targetPosition
});
let [path, labelX, labelY] = $derived(
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
sourcePosition: props.sourcePosition,
targetPosition: props.targetPosition
})
);
const edges = useEdges();
function onClick() {
edges.update((eds) => eds.filter((e) => e.id !== $$props.id));
edges.current = edges.current.filter((e) => e.id !== props.id);
}
</script>
<BaseEdge {path} {labelX} {labelY} {...$$props} />
<BaseEdge {path} {labelX} {labelY} {...props} />
<EdgeLabelRenderer>
<button
style:transform={`translate(-50%,-50%) translate(${labelX}px,${labelY}px)`}
class="edge-button"
on:click={onClick}
>
</button>
</EdgeLabelRenderer>
<EdgeLabel x={labelX} y={labelY} selectEdgeOnClick>
<button class="edge-button" onclick={onClick}> </button>
</EdgeLabel>
<style>
.edge-button {
border-radius: 50%;
position: absolute;
border: none;
width: 1rem;
height: 1rem;
@@ -48,7 +37,6 @@
justify-content: center;
align-items: center;
font-size: 8px;
pointer-events: all;
cursor: pointer;
background-color: #eee;
}
@@ -1,12 +1,21 @@
<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>;
$$restProps;
let {
data = { label: 'Node' },
positionAbsoluteX = 0,
positionAbsoluteY = 0
}: NodeProps<BuiltInNode> = $props();
export let data: { label: string } = { label: 'Node' };
export let positionAbsoluteX: number = 0;
export let positionAbsoluteY: number = 0;
useOnSelectionChange(({ nodes, edges }) => {
console.log('on selection changed via hook', { nodes, edges });
});
</script>
<div class="custom">
@@ -1,15 +1,12 @@
<script lang="ts">
import type { BuiltInNode, NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps<BuiltInNode>;
$$restProps;
export let data: { label: string } = { label: 'Node' };
let { data = { label: 'Node' } }: NodeProps<BuiltInNode> = $props();
</script>
<div class="custom">
<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>
<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">
import { useNodesInitialized, useInitialized } from '@xyflow/svelte';
import { useNodesInitialized, useViewportInitialized } from '@xyflow/svelte';
const nodesInitialized = useNodesInitialized();
const initialized = useInitialized();
const viewportInitialized = useViewportInitialized();
$: {
if (nodesInitialized) {
$effect(() => {
if (viewportInitialized.current) {
console.log('viewportInitialized');
}
});
$effect(() => {
if (nodesInitialized.current) {
console.log('nodes initialized');
}
}
$: {
if (initialized) {
console.log('initialized');
}
}
});
</script>
@@ -1,16 +1,17 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Background, MiniMap, type Node, type Edge } from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
const nodes = writable<Node[]>([]);
const edges = writable<Edge[]>([]);
let nodes = $state.raw<Node[]>([]);
let edges = $state.raw<Edge[]>([]);
const resetNodesArray = () => {
const node1: Node = {
id: 'c1',
data: { label: '1' },
width: 100,
height: 100,
position: { x: 0, y: 0 }
};
@@ -20,14 +21,14 @@
position: { x: 100, y: 0 }
};
$nodes = [node1, node2];
nodes = [node1, node2];
};
</script>
<button on:click={resetNodesArray}> Reset </button>
<button onclick={resetNodesArray}> Reset </button>
<div style:height="100vh">
<SvelteFlow {nodes} {edges} fitView>
<SvelteFlow bind:nodes bind:edges fitView>
<Controls />
<Background />
<MiniMap />
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -29,7 +28,8 @@
id,
data,
position,
type: 'default'
type: 'default',
selected: true
};
nodeItems.push(node);
@@ -46,11 +46,11 @@
}
}
const nodes = writable(nodeItems);
const edges = writable(edgeItems);
let nodes = $state.raw(nodeItems);
let edges = $state.raw(edgeItems);
</script>
<SvelteFlow {nodes} {edges} fitView minZoom={0.2}>
<SvelteFlow bind:nodes bind:edges fitView minZoom={0.2} onlyRenderVisibleElements>
<Controls />
<Background variant={BackgroundVariant.Lines} />
<MiniMap />
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -7,7 +6,8 @@
BackgroundVariant,
MiniMap,
type NodeTypes,
type Node
type Node,
type Edge
} from '@xyflow/svelte';
import '@xyflow/svelte/dist/style.css';
@@ -18,7 +18,7 @@
default: DebugNode
};
const nodes = writable<Node[]>([
let nodes = $state.raw<Node[]>([
{
id: '1',
type: 'input',
@@ -95,7 +95,7 @@
}
]);
const edges = writable([
let edges = $state.raw<Edge[]>([
{
id: 'e1-2',
source: '1',
@@ -115,13 +115,9 @@
{ id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 },
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' }
]);
$: {
console.log($nodes);
}
</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 />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
@@ -3,12 +3,23 @@
type $$Props = NodeProps;
export let id: string;
export let positionAbsoluteX: number = 0;
export let positionAbsoluteY: number = 0;
export let zIndex: number = 0;
interface Props {
id: string;
positionAbsoluteX?: number;
positionAbsoluteY?: number;
zIndex?: number;
[key: string]: any
}
$$restProps;
let {
id,
positionAbsoluteX = 0,
positionAbsoluteY = 0,
zIndex = 0,
...rest
}: Props = $props();
rest;
</script>
<Handle type="target" position={Position.Top} />
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -12,7 +11,7 @@
import '@xyflow/svelte/dist/style.css';
const nodes = writable([
let nodes = $state.raw([
{
id: 'A',
position: { x: 0, y: 0 },
@@ -20,27 +19,25 @@
},
{ 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 updateViewport = () => {
$viewport.x += 10;
$viewport = $viewport;
viewport.x += 10;
};
viewport.subscribe((vp) => {
console.log('viewport update', vp);
});
$inspect(viewport);
</script>
<SvelteFlow {nodes} {edges} {viewport}>
<SvelteFlow bind:nodes bind:edges bind:viewport>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<Panel>
<button on:click={updateViewport}>update viewport</button>
<button on:click={() => fitView()}>fitView</button>
<button onclick={updateViewport}>update viewport</button>
<button onclick={() => fitView()}>fitView</button>
</Panel>
</SvelteFlow>
@@ -1,19 +1,18 @@
<script context="module" lang="ts">
<script module lang="ts">
import type { Node } from '@xyflow/svelte';
type TextNodeType = Node<{ text: string }, 'text'>;
type UppercaseNodeType = Node<{ text: string }, 'uppercase'>;
type ResultNodeType = Node<{}, 'result'>;
export function isTextNode(node: any): node is TextNodeType | UppercaseNode {
return node.type === 'text' || node.type === 'uppercase';
export function isTextNode(node: any): node is TextNodeType | UppercaseNodeType {
return !node || !node.type ? false : node.type === 'text' || node.type === 'uppercase';
}
export type MyNode = TextNodeType | UppercaseNodeType | ResultNodeType;
</script>
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -35,7 +34,7 @@
result: ResultNode
};
const nodes = writable<MyNode[]>([
let nodes = $state.raw<MyNode[]>([
{
id: '1',
type: 'text',
@@ -60,7 +59,6 @@
},
position: { x: 0, y: 100 }
},
{
id: '3',
type: 'result',
@@ -69,7 +67,7 @@
}
]);
const edges = writable<Edge[]>([
let edges = $state.raw<Edge[]>([
{
id: 'e1-1a',
source: '1',
@@ -88,7 +86,7 @@
]);
</script>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView>
<SvelteFlow bind:nodes bind:edges {nodeTypes} fitView>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
@@ -2,24 +2,24 @@
import {
Handle,
Position,
useHandleConnections,
useNodeConnections,
useNodesData,
type NodeProps
type NodeProps,
type Node
} from '@xyflow/svelte';
import { isTextNode, type MyNode } from './+page.svelte';
type $$Props = NodeProps;
let { id }: NodeProps = $props();
export let id: $$Props['id'];
$$restProps;
const connections = useHandleConnections({
nodeId: id,
type: 'target'
const connections = useNodeConnections({
id: id,
handleType: 'target'
});
$: nodeData = useNodesData<MyNode>($connections.map((connection) => connection.source));
$: textNodes = $nodeData.filter(isTextNode);
let nodeData = $derived(
useNodesData<MyNode>(connections.current.map((connection) => connection.source))
);
let textNodes = $derived(nodeData.current.filter(isTextNode));
</script>
<div class="custom">
@@ -1,13 +1,9 @@
<script lang="ts">
import { Handle, Position, type NodeProps, useSvelteFlow } from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
export let data: $$Props['data'];
let { id, data }: NodeProps = $props();
const { updateNodeData } = useSvelteFlow();
$$restProps;
</script>
<div class="custom">
@@ -15,7 +11,7 @@
<div>
<input
value={data.text}
on:input={(evt) => updateNodeData(id, { text: evt.currentTarget.value })}
oninput={(evt) => updateNodeData(id, { text: evt.currentTarget.value })}
/>
</div>
<Handle type="source" position={Position.Right} />
@@ -2,39 +2,33 @@
import {
Handle,
Position,
useHandleConnections,
useNodeConnections,
useNodesData,
useSvelteFlow,
type Node,
type NodeProps
} from '@xyflow/svelte';
import { isTextNode, type MyNode } from './+page.svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
export let data: $$Props['data'];
$$restProps;
let { id }: NodeProps<Node<{ text: string }>> = $props();
const { updateNodeData } = useSvelteFlow();
const connections = useHandleConnections({
nodeId: id,
type: 'target'
const connections = useNodeConnections({
id: id,
handleType: 'target'
});
$: nodeData = useNodesData<MyNode>($connections[0]?.source);
$: textNode = isTextNode($nodeData) ? $nodeData : null;
let nodeData = $derived(useNodesData<MyNode>(connections.current[0]?.source));
let textNodeData = $derived(isTextNode(nodeData.current) ? nodeData.current.data.text : null);
$: console.log(textNode?.data, data);
$: {
const input = textNode?.data.text.toUpperCase() ?? '';
$effect.pre(() => {
const input = textNodeData?.toUpperCase() ?? '';
updateNodeData(id, { text: input });
console.log('updatedNodeData with', input);
}
});
</script>
<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>
<Handle type="source" position={Position.Right} />
</div>
@@ -1,12 +1,11 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteFlow, Controls, Background, BackgroundVariant, MiniMap } from '@xyflow/svelte';
import Sidebar from './Sidebar.svelte';
import '@xyflow/svelte/dist/style.css';
const nodes = writable([
let nodes = $state.raw([
{
id: '1',
type: 'input',
@@ -27,7 +26,7 @@
}
]);
const edges = writable([
let edges = $state.raw([
{
id: '1-2',
type: 'default',
@@ -45,7 +44,7 @@
</script>
<main>
<SvelteFlow {nodes} {edges} fitView>
<SvelteFlow bind:nodes bind:edges fitView maxZoom={4}>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
@@ -1,5 +1,5 @@
<script lang="ts">
import { useEdges, useNodes, useSvelteFlow } from '@xyflow/svelte';
import { useEdges, useNodes, useSvelteFlow, useViewport } from '@xyflow/svelte';
const {
zoomIn,
@@ -9,53 +9,62 @@
setCenter,
setViewport,
getViewport,
viewport,
toObject,
deleteElements
} = useSvelteFlow();
const nodes = useNodes();
const edges = useEdges();
let nodes = useNodes();
let edges = useEdges();
let viewport = useViewport();
const deleteNode = () => {
$nodes.shift();
$nodes = $nodes;
//TODO: do we really want to allow this?
// nodes.shift();
nodes.current.shift();
nodes.current = [...nodes.current];
};
</script>
<aside>
<div class="label">Functions:</div>
<button on:click={() => zoomIn()}>zoom in</button>
<button on:click={() => zoomOut({ duration: 1000 })}>zoom out transition</button>
<button on:click={() => setZoom(2)}>set zoom</button>
<button on:click={() => fitView()}>fitView</button>
<button on:click={() => setCenter(0, 0)}>setCenter 0, 0</button>
<button on:click={() => setViewport({ x: 100, y: 100, zoom: 2 })}>setViewport</button>
<button on:click={() => console.log(getViewport())}>getViewport</button>
<button onclick={() => zoomIn()}>zoom in</button>
<button onclick={() => zoomOut({ duration: 1000 })}>zoom out transition</button>
<button onclick={() => setZoom(2)}>set zoom</button>
<button onclick={() => fitView({ duration: 600 })}>fitView</button>
<button onclick={() => fitView({ duration: 600, ease: (t) => +t, interpolate: 'linear' })}
>fitView linear</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
>
<button on:click={() => deleteElements({ nodes: [{ id: $nodes[0].id }] })}>delete node</button>
<button on:click={() => deleteElements({ nodes: $nodes.map((node) => ({ id: node.id })) })}
<button onclick={() => deleteElements({ nodes: [{ id: nodes.current[0].id }] })}
>delete node</button
>
<button onclick={() => deleteElements({ nodes: nodes.current.map((node) => ({ id: node.id })) })}
>deleteElements</button
>
<button on:click={() => deleteNode()}>delete via store</button>
<button onclick={() => deleteNode()}>delete via store</button>
<button
on:click={() => {
onclick={() => {
const { nodes, edges, viewport } = toObject();
console.log(nodes, edges, viewport);
}}>toObject</button
>
<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>
{/each}
<div class="label">Viewport:</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>
</aside>
@@ -1,23 +1,20 @@
<script lang="ts">
import { Handle, Position, type NodeProps, useUpdateNodeInternals } from '@xyflow/svelte';
type $$Props = NodeProps;
export let id: $$Props['id'];
$$restProps;
let { id }: NodeProps = $props();
const updateNodeInternals = useUpdateNodeInternals();
$: handleCount = 1;
let handleCount = $state(1);
const onClick = () => {
handleCount += 1;
updateNodeInternals(id);
updateNodeInternals();
};
</script>
<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}
<Handle type="source" position={Position.Bottom} id={`${i}`} style={`left: ${i * 10}px;`} />
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -19,7 +18,7 @@
custom: CustomNode
};
const nodes = writable([
let nodes = $state.raw([
{
id: '1',
type: 'custom',
@@ -40,7 +39,7 @@
}
]);
const edges = writable([
let edges = $state.raw([
{
id: '1-2',
source: '1',
@@ -56,17 +55,19 @@
const { updateNode } = useSvelteFlow();
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>
<main>
<SvelteFlow {nodes} {edges} {nodeTypes} fitView>
<SvelteFlow bind:nodes bind:edges {nodeTypes} fitView>
<Controls />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Panel><button on:click={updateNodePosition}>update node</button></Panel>
<Panel><button onclick={updateNodePosition}>update node</button></Panel>
</SvelteFlow>
</main>
@@ -1,5 +1,4 @@
<script lang="ts">
import { writable } from 'svelte/store';
import {
SvelteFlow,
Controls,
@@ -17,7 +16,7 @@
targetPosition: Position.Left
};
const nodes = writable([
let nodes = $state.raw([
{
id: '0',
position: { x: 0, y: 150 },
@@ -29,7 +28,7 @@
{ 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';
@@ -39,8 +38,8 @@
</script>
<SvelteFlow
{nodes}
{edges}
bind:nodes
bind:edges
fitView
minZoom={0.1}
maxZoom={2.5}
@@ -3,7 +3,11 @@
import Flow from './Flow.svelte';
export let data: { flowConfig: FlowConfig };
let {
data
}: {
data: { flowConfig: FlowConfig };
} = $props();
</script>
<SvelteFlowProvider>
@@ -1,19 +1,25 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { Background, Controls, MiniMap, Panel, SvelteFlow } from '@xyflow/svelte';
import {
Background,
Controls,
MiniMap,
Panel,
SvelteFlow,
type SvelteFlowProps
} from '@xyflow/svelte';
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
const nodes = writable(flowConfig.flowProps?.nodes);
const edges = writable(flowConfig.flowProps?.edges);
let nodes = $state.raw(flowConfig.flowProps?.nodes ?? []);
let edges = $state.raw(flowConfig.flowProps?.edges ?? []);
const flowProps = { ...flowConfig.flowProps, nodes, edges };
const flowProps: SvelteFlowProps = { ...flowConfig.flowProps };
</script>
<SvelteFlow {...flowProps}>
<SvelteFlow {...flowProps} bind:nodes bind:edges>
{#if flowConfig.panelProps}
<Panel {...flowConfig.panelProps} />
{/if}
+4 -2
View File
@@ -5,8 +5,10 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
preprocess: vitePreprocess({ script: true }),
compilerOptions: {
runes: true
},
kit: {
// 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.
+4 -1
View File
@@ -8,7 +8,10 @@
"resolveJsonModule": true,
"skipLibCheck": 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
//
+4
View File
@@ -3,6 +3,10 @@ import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
build: {
sourcemap: true,
minify: false
},
server: {
fs: {
// 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/cli": "^2.25.0",
"@playwright/test": "^1.44.1",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"concurrently": "^7.6.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",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^3.0.2",
"rollup": "^4.18.0",
"turbo": "^2.0.3",
"typescript": "5.1.3"
"typescript": "5.4.5"
},
"packageManager": "pnpm@9.2.0"
}
+339
View File
@@ -1,5 +1,344 @@
# @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
### Minor Changes
+14 -5
View File
@@ -1,6 +1,6 @@
{
"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.",
"keywords": [
"react",
@@ -28,10 +28,19 @@
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.mjs",
"require": "./dist/umd/index.js"
"node": {
"types": "./dist/esm/index.d.ts",
"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/style.css": "./dist/style.css"
@@ -76,7 +85,7 @@
"postcss-nested": "^6.0.0",
"postcss-rename": "^0.6.1",
"react": "^18.2.0",
"typescript": "5.1.3"
"typescript": "5.4.5"
},
"rollup": {
"globals": {
@@ -90,4 +90,57 @@ function BackgroundComponent({
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);
@@ -1,34 +1,60 @@
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 {
Lines = 'lines',
Dots = 'dots',
Cross = 'cross',
}
/**
* @expand
*/
export type BackgroundProps = {
/** When multiple backgrounds are present on the page, each one should have a unique id. */
id?: string;
/** Color of the pattern */
/** Color of the pattern. */
color?: string;
/** Color of the background */
/** Color of the background. */
bgColor?: string;
/** Class applied to the container */
/** Class applied to the container. */
className?: string;
/** Class applied to the pattern */
/** Class applied to the pattern. */
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];
/** 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;
/** Offset of the pattern */
/**
* Offset of the pattern.
* @default 0
*/
offset?: number | [number, number];
/** Line width of the Line pattern */
/**
* The stroke thickness used when drawing the pattern.
* @default 1
*/
lineWidth?: number;
/** Variant of the pattern
/**
* Variant of the pattern.
* @default BackgroundVariant.Dots
* @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
* 'lines', 'dots', 'cross'
*/
variant?: BackgroundVariant;
/** Style applied to the container */
/** Style applied to the container. */
style?: CSSProperties;
};
@@ -2,6 +2,29 @@ import cc from 'classcat';
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) {
return (
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
@@ -125,4 +125,25 @@ function ControlsComponent({
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);
@@ -3,24 +3,46 @@ import type { PanelPosition } from '@xyflow/system';
import type { FitViewOptions } from '../../types';
/**
* @expand
*/
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;
/** 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;
/** Show button for toggling interactivity */
/**
* Show button for toggling interactivity
* @default true
*/
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;
/** Callback when zoom in button is clicked */
/** Called in addition the default zoom behavior when the zoom in button is clicked. */
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;
/** 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;
/** Callback when interactivity is toggled */
/** Called when the interactive (lock) button is clicked. */
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,
* PanelPosition.BottomLeft, PanelPosition.BottomRight
*/
@@ -28,10 +50,19 @@ export type ControlProps = {
children?: ReactNode;
/** Style applied to container */
style?: React.CSSProperties;
/** ClassName applied to container */
/** Class name applied to container */
className?: string;
/**
* @default 'React Flow controls'
*/
'aria-label'?: string;
/**
* @default 'vertical'
*/
orientation?: 'horizontal' | 'vertical';
};
/**
* @expand
*/
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
@@ -15,6 +15,8 @@ import type { MiniMapProps } from './types';
const defaultWidth = 200;
const defaultHeight = 150;
const filterHidden = (node: Node) => !node.hidden;
const selector = (s: ReactFlowState) => {
const viewBB: Rect = {
x: -s.transform[0] / s.transform[2],
@@ -25,7 +27,10 @@ const selector = (s: ReactFlowState) => {
return {
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,
panZoom: s.panZoom,
translateExtent: s.translateExtent,
@@ -44,8 +49,10 @@ function MiniMapComponent<NodeType extends Node = Node>({
nodeClassName = '',
nodeBorderRadius = 5,
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,
bgColor,
maskColor,
@@ -118,7 +125,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
const onSvgNodeClick = onNodeClick
? 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);
}, [])
: undefined;
@@ -136,7 +143,7 @@ function MiniMapComponent<NodeType extends Node = Node>({
typeof maskStrokeWidth === 'number' ? maskStrokeWidth * viewScale : 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-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined,
'--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'number' ? nodeStrokeWidth : undefined,
} as CSSProperties
}
className={cc(['react-flow__minimap', className])}
@@ -176,4 +183,24 @@ function MiniMapComponent<NodeType extends Node = Node>({
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;
@@ -6,7 +6,7 @@ import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore';
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';
declare const window: any;
@@ -21,8 +21,10 @@ function MiniMapNodes<NodeType extends Node>({
nodeClassName = '',
nodeBorderRadius = 5,
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,
onClick,
}: MiniMapNodesProps<NodeType>) {
@@ -36,11 +38,13 @@ function MiniMapNodes<NodeType extends Node>({
return (
<>
{nodeIds.map((nodeId) => (
// The split of responsibilities between MiniMapNodes and
// 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`.
/*
* The split of responsibilities between MiniMapNodes and
* 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`.
*/
<NodeComponentWrapper<NodeType>
key={nodeId}
id={nodeId}
@@ -80,8 +84,9 @@ function NodeComponentWrapperInner<NodeType extends Node>({
shapeRendering: string;
}) {
const { node, x, y, width, height } = useStore((s) => {
const node = s.nodeLookup.get(id) as InternalNode<NodeType>;
const { x, y } = node.internals.positionAbsolute;
const { internals } = s.nodeLookup.get(id)!;
const node = internals.userNode as NodeType;
const { x, y } = internals.positionAbsolute;
const { width, height } = getNodeDimensions(node);
return {
@@ -6,47 +6,97 @@ import type { Node } from '../../types';
export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
/**
* @expand
*/
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>;
/** Stroke color of nodes on minimap */
/**
* Stroke color of nodes on minimap.
* @default "transparent"
*/
nodeStrokeColor?: string | GetMiniMapNodeAttribute<NodeType>;
/** ClassName applied to nodes on minimap */
/**
* Class name applied to nodes on minimap.
* @default ""
*/
nodeClassName?: string | GetMiniMapNodeAttribute<NodeType>;
/** Border radius of nodes on minimap */
/**
* Border radius of nodes on minimap.
* @default 5
*/
nodeBorderRadius?: number;
/** Stroke width of nodes on minimap */
/**
* Stroke width of nodes on minimap.
* @default 2
*/
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>;
/** Background color of minimap */
/** Background color of minimap. */
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;
/** Stroke color of mask representing viewport */
/**
* Stroke color of mask representing viewport.
* @default transparent
*/
maskStrokeColor?: string;
/** Stroke width of mask representing viewport */
/**
* Stroke width of mask representing viewport.
* @default 1
*/
maskStrokeWidth?: number;
/** Position of minimap on pane
/**
* Position of minimap on pane.
* @default PanelPosition.BottomRight
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
* PanelPosition.BottomLeft, PanelPosition.BottomRight
*/
position?: PanelPosition;
/** Callback caled when minimap is clicked*/
/** Callback called when minimap is clicked. */
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;
/** 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;
/** 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;
/** 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;
/** Invert direction when panning the minimap viewport */
/** Invert direction when panning the minimap viewport. */
inversePan?: boolean;
/** Step size for zooming in/out on minimap */
/**
* Step size for zooming in/out on minimap.
* @default 10
*/
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;
};
@@ -57,6 +107,12 @@ export type MiniMapNodes<NodeType extends Node = Node> = Pick<
onClick?: (event: MouseEvent, nodeId: string) => void;
};
/**
* The props that are passed to the MiniMapNode component
*
* @public
* @expand
*/
export type MiniMapNodeProps = {
id: string;
x: number;
@@ -32,6 +32,7 @@ function ResizeControl({
maxWidth = Number.MAX_VALUE,
maxHeight = Number.MAX_VALUE,
keepAspectRatio = false,
resizeDirection,
shouldResize,
onResizeStart,
onResize,
@@ -56,13 +57,14 @@ function ResizeControl({
domNode: resizeControlRef.current,
nodeId: id,
getStoreItems: () => {
const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = store.getState();
const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, domNode } = store.getState();
return {
nodeLookup,
transform,
snapGrid,
snapToGrid,
nodeOrigin,
paneDomNode: domNode,
};
},
onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => {
@@ -73,8 +75,8 @@ function ResizeControl({
if (node && node.expandParent && node.parentId) {
const origin = node.origin ?? nodeOrigin;
const width = change.width ?? node.measured.width!;
const height = change.height ?? node.measured.height!;
const width = change.width ?? node.measured.width ?? 0;
const height = change.height ?? node.measured.height ?? 0;
const child: ParentExpandChild = {
id: node.id,
@@ -98,8 +100,10 @@ function ResizeControl({
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
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.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
}
@@ -114,11 +118,12 @@ function ResizeControl({
}
if (change.width !== undefined && change.height !== undefined) {
const setAttributes = !resizeDirection ? true : resizeDirection === 'horizontal' ? 'width' : 'height';
const dimensionChange: NodeDimensionChange = {
id,
type: 'dimensions',
resizing: true,
setAttributes: true,
setAttributes,
dimensions: {
width: change.width,
height: change.height,
@@ -139,11 +144,15 @@ function ResizeControl({
triggerNodeChanges(changes);
},
onEnd: () => {
onEnd: ({ width, height }) => {
const dimensionChange: NodeDimensionChange = {
id: id,
type: 'dimensions',
resizing: false,
dimensions: {
width,
height,
},
};
store.getState().triggerNodeChanges([dimensionChange]);
},
@@ -159,6 +168,7 @@ function ResizeControl({
maxHeight,
},
keepAspectRatio,
resizeDirection,
onResizeStart,
onResize,
onResizeEnd,
@@ -200,4 +210,9 @@ export function ResizeControlLine(props: ResizeControlLineProps) {
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);

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