implemented new fitView logic
This commit is contained in:
@@ -38,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -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',
|
||||
|
||||
@@ -56,8 +56,18 @@ const initialEdges: Edge[] = [
|
||||
const defaultEdgeOptions = {};
|
||||
|
||||
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 +114,7 @@ const BasicFlow = () => {
|
||||
]);
|
||||
|
||||
setEdges([{ id: 'a-b', source: 'a', target: 'b' }]);
|
||||
fitView();
|
||||
};
|
||||
|
||||
const onUpdateNode = () => {
|
||||
@@ -117,6 +128,7 @@ const BasicFlow = () => {
|
||||
position: { x: Math.random() * 300, y: Math.random() * 300 },
|
||||
className: 'light',
|
||||
});
|
||||
fitView();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -134,6 +146,9 @@ const BasicFlow = () => {
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
fitViewOptions={{
|
||||
padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 },
|
||||
}}
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
selectNodesOnDrag={false}
|
||||
elevateEdgesOnSelect
|
||||
|
||||
80
examples/react/src/examples/BrokenNodes/index.tsx
Normal file
80
examples/react/src/examples/BrokenNodes/index.tsx
Normal file
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
useEdgesState,
|
||||
useOnSelectionChange,
|
||||
OnSelectionChangeParams,
|
||||
OnSelectionChangeFunc,
|
||||
} from '@xyflow/react';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/kit": "^2.16.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.22.0",
|
||||
"@typescript-eslint/parser": "^8.22.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"prettier": "^3.4.2",
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.20.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-svelte": "^3.5.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.19.5",
|
||||
"svelte-check": "^4.1.4",
|
||||
"svelte": "^5.25.8",
|
||||
"svelte-check": "^4.1.5",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.11"
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.2.5"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,158 @@
|
||||
# @xyflow/react
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/react",
|
||||
"version": "12.4.2",
|
||||
"version": "12.5.5",
|
||||
"description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -28,6 +28,7 @@
|
||||
"module": "dist/esm/index.js",
|
||||
"types": "dist/esm/index.d.ts",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"node": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
@@ -84,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 it’s 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, it’s 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, it’s 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;
|
||||
|
||||
@@ -74,8 +74,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,
|
||||
@@ -99,8 +99,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;
|
||||
}
|
||||
@@ -140,11 +142,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]);
|
||||
},
|
||||
@@ -201,4 +207,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);
|
||||
|
||||
@@ -3,6 +3,30 @@ import { ResizeControlVariant, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSI
|
||||
import { NodeResizeControl } from './NodeResizeControl';
|
||||
import type { NodeResizerProps } from './types';
|
||||
|
||||
/**
|
||||
* The `<NodeResizer />` component can be used to add a resize functionality to your
|
||||
* nodes. It renders draggable controls around the node to resize in all directions.
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
*```jsx
|
||||
*import { memo } from 'react';
|
||||
*import { Handle, Position, NodeResizer } from '@xyflow/react';
|
||||
*
|
||||
*function ResizableNode({ data }) {
|
||||
* return (
|
||||
* <>
|
||||
* <NodeResizer minWidth={100} minHeight={30} />
|
||||
* <Handle type="target" position={Position.Left} />
|
||||
* <div style={{ padding: 10 }}>{data.label}</div>
|
||||
* <Handle type="source" position={Position.Right} />
|
||||
* </>
|
||||
* );
|
||||
*};
|
||||
*
|
||||
*export default memo(ResizableNode);
|
||||
*```
|
||||
*/
|
||||
export function NodeResizer({
|
||||
nodeId,
|
||||
isVisible = true,
|
||||
|
||||
@@ -9,43 +9,68 @@ import type {
|
||||
OnResizeEnd,
|
||||
} from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type NodeResizerProps = {
|
||||
/** Id of the node it is resizing
|
||||
/**
|
||||
* Id of the node it is resizing.
|
||||
* @remarks optional if used inside custom node
|
||||
*/
|
||||
nodeId?: string;
|
||||
/** Color of the resize handle */
|
||||
/** Color of the resize handle. */
|
||||
color?: string;
|
||||
/** ClassName applied to handle */
|
||||
/** Class name applied to handle. */
|
||||
handleClassName?: string;
|
||||
/** Style applied to handle */
|
||||
/** Style applied to handle. */
|
||||
handleStyle?: CSSProperties;
|
||||
/** ClassName applied to line */
|
||||
/** Class name applied to line. */
|
||||
lineClassName?: string;
|
||||
/** Style applied to line */
|
||||
/** Style applied to line. */
|
||||
lineStyle?: CSSProperties;
|
||||
/** Are the controls visible */
|
||||
/**
|
||||
* Are the controls visible.
|
||||
* @default true
|
||||
*/
|
||||
isVisible?: boolean;
|
||||
/** Minimum width of node */
|
||||
/**
|
||||
* Minimum width of node.
|
||||
* @default 10
|
||||
*/
|
||||
minWidth?: number;
|
||||
/** Minimum height of node */
|
||||
/**
|
||||
* Minimum height of node.
|
||||
* @default 10
|
||||
*/
|
||||
minHeight?: number;
|
||||
/** Maximum width of node */
|
||||
/**
|
||||
* Maximum width of node.
|
||||
* @default Number.MAX_VALUE
|
||||
*/
|
||||
maxWidth?: number;
|
||||
/** Maximum height of node */
|
||||
/**
|
||||
* Maximum height of node.
|
||||
* @default Number.MAX_VALUE
|
||||
*/
|
||||
maxHeight?: number;
|
||||
/** Keep aspect ratio when resizing */
|
||||
/**
|
||||
* Keep aspect ratio when resizing.
|
||||
* @default false
|
||||
*/
|
||||
keepAspectRatio?: boolean;
|
||||
/** Callback to determine if node should resize */
|
||||
/** Callback to determine if node should resize. */
|
||||
shouldResize?: ShouldResize;
|
||||
/** Callback called when resizing starts */
|
||||
/** Callback called when resizing starts. */
|
||||
onResizeStart?: OnResizeStart;
|
||||
/** Callback called when resizing */
|
||||
/** Callback called when resizing. */
|
||||
onResize?: OnResize;
|
||||
/** Callback called when resizing ends */
|
||||
/** Callback called when resizing ends. */
|
||||
onResizeEnd?: OnResizeEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ResizeControlProps = Pick<
|
||||
NodeResizerProps,
|
||||
| 'nodeId'
|
||||
@@ -60,12 +85,15 @@ export type ResizeControlProps = Pick<
|
||||
| 'onResize'
|
||||
| 'onResizeEnd'
|
||||
> & {
|
||||
/** Position of the control
|
||||
/**
|
||||
* Position of the control.
|
||||
* @example ControlPosition.TopLeft, ControlPosition.TopRight,
|
||||
* ControlPosition.BottomLeft, ControlPosition.BottomRight
|
||||
*/
|
||||
position?: ControlPosition;
|
||||
/** Variant of the control
|
||||
/**
|
||||
* Variant of the control.
|
||||
* @default "handle"
|
||||
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line
|
||||
*/
|
||||
variant?: ResizeControlVariant;
|
||||
@@ -74,6 +102,9 @@ export type ResizeControlProps = Pick<
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ResizeControlLineProps = ResizeControlProps & {
|
||||
position?: ControlLinePosition;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,41 @@ const storeSelector = (state: ReactFlowState) => ({
|
||||
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
|
||||
});
|
||||
|
||||
/**
|
||||
* This component can render a toolbar or tooltip to one side of a custom node. This
|
||||
* toolbar doesn't scale with the viewport so that the content is always visible.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { memo } from 'react';
|
||||
*import { Handle, Position, NodeToolbar } from '@xyflow/react';
|
||||
*
|
||||
*function CustomNode({ data }) {
|
||||
* return (
|
||||
* <>
|
||||
* <NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
|
||||
* <button>delete</button>
|
||||
* <button>copy</button>
|
||||
* <button>expand</button>
|
||||
* </NodeToolbar>
|
||||
*
|
||||
* <div style={{ padding: '10px 20px' }}>
|
||||
* {data.label}
|
||||
* </div>
|
||||
*
|
||||
* <Handle type="target" position={Position.Left} />
|
||||
* <Handle type="source" position={Position.Right} />
|
||||
* </>
|
||||
* );
|
||||
*};
|
||||
*
|
||||
*export default memo(CustomNode);
|
||||
*```
|
||||
* @remarks By default, the toolbar is only visible when a node is selected. If multiple
|
||||
* nodes are selected it will not be visible to prevent overlapping toolbars or
|
||||
* clutter. You can override this behavior by setting the `isVisible` prop to `true`.
|
||||
*/
|
||||
export function NodeToolbar({
|
||||
nodeId,
|
||||
children,
|
||||
@@ -74,7 +109,7 @@ export function NodeToolbar({
|
||||
const isActive =
|
||||
typeof isVisible === 'boolean'
|
||||
? isVisible
|
||||
: nodes.size === 1 && nodes.values().next().value.selected && selectedNodesCount === 1;
|
||||
: nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1;
|
||||
|
||||
if (!isActive || !nodes.size) {
|
||||
return null;
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import type { Position, Align } from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
||||
/** Id of the node, or array of ids the toolbar should be displayed at */
|
||||
/**
|
||||
* By passing in an array of node id's you can render a single tooltip for a group or collection
|
||||
* of nodes.
|
||||
*/
|
||||
nodeId?: string | string[];
|
||||
/** If true, node toolbar is visible even if node is not selected */
|
||||
/** If `true`, node toolbar is visible even if node is not selected. */
|
||||
isVisible?: boolean;
|
||||
/** Position of the toolbar relative to the node
|
||||
* @example Position.TopLeft, Position.TopRight,
|
||||
* Position.BottomLeft, Position.BottomRight
|
||||
/**
|
||||
* Position of the toolbar relative to the node.
|
||||
* @default Position.Top
|
||||
* @example Position.TopLeft, Position.TopRight, Position.BottomLeft, Position.BottomRight
|
||||
*/
|
||||
position?: Position;
|
||||
/** Offset the toolbar from the node */
|
||||
/**
|
||||
* The space between the node and the toolbar, measured in pixels.
|
||||
* @default 10
|
||||
*/
|
||||
offset?: number;
|
||||
/** Align the toolbar relative to the node
|
||||
/**
|
||||
* Align the toolbar relative to the node.
|
||||
* @default "center"
|
||||
* @example Align.Start, Align.Center, Align.End
|
||||
*/
|
||||
align?: Align;
|
||||
|
||||
@@ -28,33 +28,49 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
|
||||
const store = useStoreApi<NodeType, EdgeType>();
|
||||
|
||||
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
|
||||
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
|
||||
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup, fitViewQueued } = store.getState();
|
||||
|
||||
// This is essentially an `Array.reduce` in imperative clothing. Processing
|
||||
// this queue is a relatively hot path so we'd like to avoid the overhead of
|
||||
// array methods where we can.
|
||||
let next = nodes as NodeType[];
|
||||
/*
|
||||
* This is essentially an `Array.reduce` in imperative clothing. Processing
|
||||
* this queue is a relatively hot path so we'd like to avoid the overhead of
|
||||
* array methods where we can.
|
||||
*/
|
||||
let next = nodes;
|
||||
for (const payload of queueItems) {
|
||||
next = typeof payload === 'function' ? payload(next) : payload;
|
||||
}
|
||||
|
||||
if (hasDefaultNodes) {
|
||||
setNodes(next);
|
||||
} else if (onNodesChange) {
|
||||
onNodesChange(
|
||||
getElementsDiffChanges({
|
||||
items: next,
|
||||
lookup: nodeLookup,
|
||||
}) as NodeChange<NodeType>[]
|
||||
);
|
||||
} else {
|
||||
// When a controlled flow is used we need to collect the changes
|
||||
const changes = getElementsDiffChanges({
|
||||
items: next,
|
||||
lookup: nodeLookup,
|
||||
}) as NodeChange<NodeType>[];
|
||||
|
||||
// We only want to fire onNodesChange if there are changes to the nodes
|
||||
if (changes.length > 0) {
|
||||
onNodesChange?.(changes);
|
||||
} else if (fitViewQueued) {
|
||||
// If there are no changes to the nodes, we still need to call setNodes
|
||||
// to trigger a re-render and fitView.
|
||||
window.requestAnimationFrame(() => {
|
||||
const { fitViewQueued, nodes, setNodes } = store.getState();
|
||||
if (fitViewQueued) {
|
||||
setNodes(nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const nodeQueue = useQueue<NodeType>(nodeQueueHandler);
|
||||
|
||||
const edgeQueueHandler = useCallback((queueItems: QueueItem<EdgeType>[]) => {
|
||||
const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
|
||||
|
||||
let next = edges as EdgeType[];
|
||||
let next = edges;
|
||||
for (const payload of queueItems) {
|
||||
next = typeof payload === 'function' ? payload(next) : payload;
|
||||
}
|
||||
|
||||
@@ -12,21 +12,27 @@ import { Queue, QueueItem } from './types';
|
||||
* @returns a Queue object
|
||||
*/
|
||||
export function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void) {
|
||||
// Because we're using a ref above, we need some way to let React know when to
|
||||
// actually process the queue. We increment this number any time we mutate the
|
||||
// queue, creating a new state to trigger the layout effect below.
|
||||
// Using a boolean dirty flag here instead would lead to issues related to
|
||||
// automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
|
||||
/*
|
||||
* Because we're using a ref above, we need some way to let React know when to
|
||||
* actually process the queue. We increment this number any time we mutate the
|
||||
* queue, creating a new state to trigger the layout effect below.
|
||||
* Using a boolean dirty flag here instead would lead to issues related to
|
||||
* automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
|
||||
*/
|
||||
const [serial, setSerial] = useState(BigInt(0));
|
||||
|
||||
// A reference of all the batched updates to process before the next render. We
|
||||
// want a reference here so multiple synchronous calls to `setNodes` etc can be
|
||||
// batched together.
|
||||
/*
|
||||
* A reference of all the batched updates to process before the next render. We
|
||||
* want a reference here so multiple synchronous calls to `setNodes` etc can be
|
||||
* batched together.
|
||||
*/
|
||||
const [queue] = useState(() => createQueue<T>(() => setSerial(n => n + BigInt(1))));
|
||||
|
||||
// Layout effects are guaranteed to run before the next render which means we
|
||||
// shouldn't run into any issues with stale state or weird issues that come from
|
||||
// rendering things one frame later than expected (we used to use `setTimeout`).
|
||||
/*
|
||||
* Layout effects are guaranteed to run before the next render which means we
|
||||
* shouldn't run into any issues with stale state or weird issues that come from
|
||||
* rendering things one frame later than expected (we used to use `setTimeout`).
|
||||
*/
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const queueItems = queue.get();
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
|
||||
import type { ConnectionLineComponent, ReactFlowState } from '../../types';
|
||||
import type { ConnectionLineComponent, Node, ReactFlowState } from '../../types';
|
||||
import { useConnection } from '../../hooks/useConnection';
|
||||
|
||||
type ConnectionLineWrapperProps = {
|
||||
type ConnectionLineWrapperProps<NodeType extends Node = Node> = {
|
||||
type: ConnectionLineType;
|
||||
component?: ConnectionLineComponent;
|
||||
component?: ConnectionLineComponent<NodeType>;
|
||||
containerStyle?: CSSProperties;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
@@ -29,7 +29,12 @@ const selector = (s: ReactFlowState) => ({
|
||||
height: s.height,
|
||||
});
|
||||
|
||||
export function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
|
||||
export function ConnectionLineWrapper<NodeType extends Node = Node>({
|
||||
containerStyle,
|
||||
style,
|
||||
type,
|
||||
component,
|
||||
}: ConnectionLineWrapperProps<NodeType>) {
|
||||
const { nodesConnectable, width, height, isValid, inProgress } = useStore(selector, shallow);
|
||||
const renderConnection = !!(width && nodesConnectable && inProgress);
|
||||
|
||||
@@ -45,21 +50,27 @@ export function ConnectionLineWrapper({ containerStyle, style, type, component }
|
||||
className="react-flow__connectionline react-flow__container"
|
||||
>
|
||||
<g className={cc(['react-flow__connection', getConnectionStatus(isValid)])}>
|
||||
<ConnectionLine style={style} type={type} CustomComponent={component} isValid={isValid} />
|
||||
<ConnectionLine<NodeType> style={style} type={type} CustomComponent={component} isValid={isValid} />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
type ConnectionLineProps = {
|
||||
type ConnectionLineProps<NodeType extends Node = Node> = {
|
||||
type: ConnectionLineType;
|
||||
style?: CSSProperties;
|
||||
CustomComponent?: ConnectionLineComponent;
|
||||
CustomComponent?: ConnectionLineComponent<NodeType>;
|
||||
isValid: boolean | null;
|
||||
};
|
||||
|
||||
const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid }: ConnectionLineProps) => {
|
||||
const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } = useConnection();
|
||||
const ConnectionLine = <NodeType extends Node = Node>({
|
||||
style,
|
||||
type = ConnectionLineType.Bezier,
|
||||
CustomComponent,
|
||||
isValid,
|
||||
}: ConnectionLineProps<NodeType>) => {
|
||||
const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } =
|
||||
useConnection<NodeType>();
|
||||
|
||||
if (!inProgress) {
|
||||
return;
|
||||
|
||||
@@ -6,7 +6,52 @@ import type { ReactFlowState } from '../../types';
|
||||
|
||||
const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__edgelabel-renderer');
|
||||
|
||||
export function EdgeLabelRenderer({ children }: { children: ReactNode }) {
|
||||
export type EdgeLabelRendererProps = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Edges are SVG-based. If you want to render more complex labels you can use the
|
||||
* `<EdgeLabelRenderer />` component to access a div based renderer. This component
|
||||
* is a portal that renders the label in a `<div />` that is positioned on top of
|
||||
* the edges. You can see an example usage of the component in the
|
||||
* [edge label renderer example](/examples/edges/edge-label-renderer).
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import React from 'react';
|
||||
* import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react';
|
||||
*
|
||||
* export function CustomEdge({ id, data, ...props }) {
|
||||
* const [edgePath, labelX, labelY] = getBezierPath(props);
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* <BaseEdge id={id} path={edgePath} />
|
||||
* <EdgeLabelRenderer>
|
||||
* <div
|
||||
* style={{
|
||||
* position: 'absolute',
|
||||
* transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
* background: '#ffcc00',
|
||||
* padding: 10,
|
||||
* }}
|
||||
* className="nodrag nopan"
|
||||
* >
|
||||
* {data.label}
|
||||
* </div>
|
||||
* </EdgeLabelRenderer>
|
||||
* </>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @remarks The `<EdgeLabelRenderer />` has no pointer events by default. If you want to
|
||||
* add mouse interactions you need to set the style `pointerEvents: all` and add
|
||||
* the `nopan` class on the label or the element you want to interact with.
|
||||
*/
|
||||
export function EdgeLabelRenderer({ children }: EdgeLabelRendererProps) {
|
||||
const edgeLabelRenderer = useStore(selector);
|
||||
|
||||
if (!edgeLabelRenderer) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react';
|
||||
import { useState, useMemo, useRef, type KeyboardEvent, useCallback, JSX } from 'react';
|
||||
import cc from 'classcat';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import {
|
||||
@@ -136,28 +136,28 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
|
||||
|
||||
const onEdgeDoubleClick = onDoubleClick
|
||||
? (event: React.MouseEvent) => {
|
||||
onDoubleClick(event, { ...edge });
|
||||
}
|
||||
onDoubleClick(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeContextMenu = onContextMenu
|
||||
? (event: React.MouseEvent) => {
|
||||
onContextMenu(event, { ...edge });
|
||||
}
|
||||
onContextMenu(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeMouseEnter = onMouseEnter
|
||||
? (event: React.MouseEvent) => {
|
||||
onMouseEnter(event, { ...edge });
|
||||
}
|
||||
onMouseEnter(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeMouseMove = onMouseMove
|
||||
? (event: React.MouseEvent) => {
|
||||
onMouseMove(event, { ...edge });
|
||||
}
|
||||
onMouseMove(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeMouseLeave = onMouseLeave
|
||||
? (event: React.MouseEvent) => {
|
||||
onMouseLeave(event, { ...edge });
|
||||
}
|
||||
onMouseLeave(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
@@ -4,6 +4,33 @@ import cc from 'classcat';
|
||||
import { EdgeText } from './EdgeText';
|
||||
import type { BaseEdgeProps } from '../../types';
|
||||
|
||||
/**
|
||||
* The `<BaseEdge />` component gets used internally for all the edges. It can be
|
||||
* used inside a custom edge and handles the invisible helper edge and the edge label
|
||||
* for you.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { BaseEdge } from '@xyflow/react';
|
||||
*
|
||||
*export function CustomEdge({ sourceX, sourceY, targetX, targetY, ...props }) {
|
||||
* const [edgePath] = getStraightPath({
|
||||
* sourceX,
|
||||
* sourceY,
|
||||
* targetX,
|
||||
* targetY,
|
||||
* });
|
||||
*
|
||||
* return <BaseEdge path={edgePath} {...props} />;
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks If you want to use an edge marker with the [`<BaseEdge />`](/api-reference/components/base-edge) component,
|
||||
* you can pass the `markerStart` or `markerEnd` props passed to your custom edge
|
||||
* through to the [`<BaseEdge />`](/api-reference/components/base-edge) component.
|
||||
* You can see all the props passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type.
|
||||
*/
|
||||
export function BaseEdge({
|
||||
path,
|
||||
labelX,
|
||||
|
||||
@@ -61,7 +61,34 @@ function createBezierEdge(params: { isInternal: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that can be used inside a custom edge to render a bezier curve.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
* import { BezierEdge } from '@xyflow/react';
|
||||
*
|
||||
* function CustomEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }) {
|
||||
* return (
|
||||
* <BezierEdge
|
||||
* sourceX={sourceX}
|
||||
* sourceY={sourceY}
|
||||
* targetX={targetX}
|
||||
* targetY={targetY}
|
||||
* sourcePosition={sourcePosition}
|
||||
* targetPosition={targetPosition}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const BezierEdge = createBezierEdge({ isInternal: false });
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const BezierEdgeInternal = createBezierEdge({ isInternal: true });
|
||||
|
||||
BezierEdge.displayName = 'BezierEdge';
|
||||
|
||||
@@ -27,6 +27,9 @@ export interface EdgeAnchorProps extends SVGAttributes<SVGGElement> {
|
||||
|
||||
const EdgeUpdaterClassName = 'react-flow__edgeupdater';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function EdgeAnchor({
|
||||
position,
|
||||
centerX,
|
||||
|
||||
@@ -8,9 +8,9 @@ function EdgeTextComponent({
|
||||
x,
|
||||
y,
|
||||
label,
|
||||
labelStyle = {},
|
||||
labelStyle,
|
||||
labelShowBg = true,
|
||||
labelBgStyle = {},
|
||||
labelBgStyle,
|
||||
labelBgPadding = [2, 4],
|
||||
labelBgBorderRadius = 2,
|
||||
children,
|
||||
@@ -34,7 +34,7 @@ function EdgeTextComponent({
|
||||
}
|
||||
}, [label]);
|
||||
|
||||
if (typeof label === 'undefined' || !label) {
|
||||
if (!label) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -73,4 +73,30 @@ function EdgeTextComponent({
|
||||
|
||||
EdgeTextComponent.displayName = 'EdgeText';
|
||||
|
||||
/**
|
||||
* You can use the `<EdgeText />` component as a helper component to display text
|
||||
* within your custom edges.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import { EdgeText } from '@xyflow/react';
|
||||
*
|
||||
* export function CustomEdgeLabel({ label }) {
|
||||
* return (
|
||||
* <EdgeText
|
||||
* x={100}
|
||||
* y={100}
|
||||
* label={label}
|
||||
* labelStyle={{ fill: 'white' }}
|
||||
* labelShowBg
|
||||
* labelBgStyle={{ fill: 'red' }}
|
||||
* labelBgPadding={[2, 4]}
|
||||
* labelBgBorderRadius={2}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
*```
|
||||
*/
|
||||
export const EdgeText = memo(EdgeTextComponent);
|
||||
|
||||
@@ -7,9 +7,11 @@ import type { SimpleBezierEdgeProps } from '../../types';
|
||||
export interface GetSimpleBezierPathParams {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
/** @default Position.Bottom */
|
||||
sourcePosition?: Position;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
/** @default Position.Top */
|
||||
targetPosition?: Position;
|
||||
}
|
||||
|
||||
@@ -29,6 +31,19 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number]
|
||||
return [x1, 0.5 * (y1 + y2)];
|
||||
}
|
||||
|
||||
/**
|
||||
* The `getSimpleBezierPath` util returns everything you need to render a simple
|
||||
* bezier edge between two nodes.
|
||||
* @public
|
||||
* @returns
|
||||
* - `path`: the path to use in an SVG `<path>` element.
|
||||
* - `labelX`: the `x` position you can use to render a label for this edge.
|
||||
* - `labelY`: the `y` position you can use to render a label for this edge.
|
||||
* - `offsetX`: the absolute difference between the source `x` position and the `x` position of the
|
||||
* middle of this path.
|
||||
* - `offsetY`: the absolute difference between the source `y` position and the `y` position of the
|
||||
* middle of this path.
|
||||
*/
|
||||
export function getSimpleBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
@@ -80,8 +95,8 @@ function createSimpleBezierEdge(params: { isInternal: boolean }) {
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetPosition = Position.Top,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
|
||||
@@ -62,7 +62,34 @@ function createSmoothStepEdge(params: { isInternal: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that can be used inside a custom edge to render a smooth step edge.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
* import { SmoothStepEdge } from '@xyflow/react';
|
||||
*
|
||||
* function CustomEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }) {
|
||||
* return (
|
||||
* <SmoothStepEdge
|
||||
* sourceX={sourceX}
|
||||
* sourceY={sourceY}
|
||||
* targetX={targetX}
|
||||
* targetY={targetY}
|
||||
* sourcePosition={sourcePosition}
|
||||
* targetPosition={targetPosition}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const SmoothStepEdge = createSmoothStepEdge({ isInternal: false });
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const SmoothStepEdgeInternal = createSmoothStepEdge({ isInternal: true });
|
||||
|
||||
SmoothStepEdge.displayName = 'SmoothStepEdge';
|
||||
|
||||
@@ -21,7 +21,34 @@ function createStepEdge(params: { isInternal: boolean }) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that can be used inside a custom edge to render a step edge.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
* import { StepEdge } from '@xyflow/react';
|
||||
*
|
||||
* function CustomEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }) {
|
||||
* return (
|
||||
* <StepEdge
|
||||
* sourceX={sourceX}
|
||||
* sourceY={sourceY}
|
||||
* targetX={targetX}
|
||||
* targetY={targetY}
|
||||
* sourcePosition={sourcePosition}
|
||||
* targetPosition={targetPosition}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const StepEdge = createStepEdge({ isInternal: false });
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const StepEdgeInternal = createStepEdge({ isInternal: true });
|
||||
|
||||
StepEdge.displayName = 'StepEdge';
|
||||
|
||||
@@ -50,7 +50,32 @@ function createStraightEdge(params: { isInternal: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that can be used inside a custom edge to render a straight line.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
* import { StraightEdge } from '@xyflow/react';
|
||||
*
|
||||
* function CustomEdge({ sourceX, sourceY, targetX, targetY }) {
|
||||
* return (
|
||||
* <StraightEdge
|
||||
* sourceX={sourceX}
|
||||
* sourceY={sourceY}
|
||||
* targetX={targetX}
|
||||
* targetY={targetY}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const StraightEdge = createStraightEdge({ isInternal: false });
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const StraightEdgeInternal = createStraightEdge({ isInternal: true });
|
||||
|
||||
StraightEdge.displayName = 'StraightEdge';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// We distinguish between internal and exported edges
|
||||
// The internal edges are used directly like custom edges and always get an id, source and target props
|
||||
// If you import an edge from the library, the id is optional and source and target are not used at all
|
||||
/*
|
||||
* We distinguish between internal and exported edges
|
||||
* The internal edges are used directly like custom edges and always get an id, source and target props
|
||||
* If you import an edge from the library, the id is optional and source and target are not used at all
|
||||
*/
|
||||
|
||||
export { SimpleBezierEdge, SimpleBezierEdgeInternal } from './SimpleBezierEdge';
|
||||
export { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge';
|
||||
|
||||
@@ -28,10 +28,14 @@ import { useNodeId } from '../../contexts/NodeIdContext';
|
||||
import { type ReactFlowState } from '../../types';
|
||||
import { fixedForwardRef } from '../../utils';
|
||||
|
||||
export interface HandleProps extends HandlePropsSystem, Omit<HTMLAttributes<HTMLDivElement>, 'id'> {
|
||||
/** Callback called when connection is made */
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type HandleProps = HandlePropsSystem &
|
||||
Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
||||
/** Callback called when connection is made */
|
||||
onConnect?: OnConnect;
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
connectOnClick: s.connectOnClick,
|
||||
@@ -42,9 +46,7 @@ const selector = (s: ReactFlowState) => ({
|
||||
const connectingSelector =
|
||||
(nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => {
|
||||
const { connectionClickStartHandle: clickHandle, connectionMode, connection } = state;
|
||||
|
||||
const { fromHandle, toHandle, isValid } = connection;
|
||||
|
||||
const connectingTo = toHandle?.nodeId === nodeId && toHandle?.id === handleId && toHandle?.type === type;
|
||||
|
||||
return {
|
||||
@@ -56,6 +58,7 @@ const connectingSelector =
|
||||
? fromHandle?.type !== type
|
||||
: nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.id,
|
||||
connectionInProcess: !!fromHandle,
|
||||
clickConnectionInProcess: !!clickHandle,
|
||||
valid: connectingTo && isValid,
|
||||
};
|
||||
};
|
||||
@@ -83,11 +86,15 @@ function HandleComponent(
|
||||
const store = useStoreApi();
|
||||
const nodeId = useNodeId();
|
||||
const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow);
|
||||
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore(
|
||||
connectingSelector(nodeId, handleId, type),
|
||||
shallow
|
||||
);
|
||||
|
||||
const {
|
||||
connectingFrom,
|
||||
connectingTo,
|
||||
clickConnecting,
|
||||
isPossibleEndHandle,
|
||||
connectionInProcess,
|
||||
clickConnectionInProcess,
|
||||
valid,
|
||||
} = useStore(connectingSelector(nodeId, handleId, type), shallow);
|
||||
if (!nodeId) {
|
||||
store.getState().onError?.('010', errorMessages['error010']());
|
||||
}
|
||||
@@ -228,12 +235,14 @@ function HandleComponent(
|
||||
connectingfrom: connectingFrom,
|
||||
connectingto: connectingTo,
|
||||
valid,
|
||||
// shows where you can start a connection from
|
||||
// and where you can end it while connecting
|
||||
/*
|
||||
* shows where you can start a connection from
|
||||
* and where you can end it while connecting
|
||||
*/
|
||||
connectionindicator:
|
||||
isConnectable &&
|
||||
(!connectionInProcess || isPossibleEndHandle) &&
|
||||
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
||||
(connectionInProcess || clickConnectionInProcess ? isConnectableEnd : isConnectableStart),
|
||||
},
|
||||
])}
|
||||
onMouseDown={onPointerDown}
|
||||
@@ -248,6 +257,28 @@ function HandleComponent(
|
||||
}
|
||||
|
||||
/**
|
||||
* The Handle component is a UI element that is used to connect nodes.
|
||||
* The `<Handle />` component is used in your [custom nodes](/learn/customization/custom-nodes)
|
||||
* to define connection points.
|
||||
*
|
||||
*@public
|
||||
*
|
||||
*@example
|
||||
*
|
||||
*```jsx
|
||||
*import { Handle, Position } from '@xyflow/react';
|
||||
*
|
||||
*export function CustomNode({ data }) {
|
||||
* return (
|
||||
* <>
|
||||
* <div style={{ padding: '10px 20px' }}>
|
||||
* {data.label}
|
||||
* </div>
|
||||
*
|
||||
* <Handle type="target" position={Position.Left} />
|
||||
* <Handle type="source" position={Position.Right} />
|
||||
* </>
|
||||
* );
|
||||
*};
|
||||
*```
|
||||
*/
|
||||
export const Handle = memo(fixedForwardRef(HandleComponent));
|
||||
|
||||
@@ -37,7 +37,6 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
disableKeyboardA11y,
|
||||
rfId,
|
||||
nodeTypes,
|
||||
nodeExtent,
|
||||
nodeClickDistance,
|
||||
onError,
|
||||
}: NodeWrapperProps<NodeType>) {
|
||||
@@ -109,8 +108,10 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
||||
|
||||
if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) {
|
||||
// this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
||||
// here we only need to call it when selectNodesOnDrag=false
|
||||
/*
|
||||
* this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
||||
* here we only need to call it when selectNodesOnDrag=false
|
||||
*/
|
||||
handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
|
||||
@@ -49,8 +49,10 @@ export function useNodeObserver({
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current) {
|
||||
// when the user programmatically changes the source or handle position, we need to update the internals
|
||||
// to make sure the edges are updated correctly
|
||||
/*
|
||||
* when the user programmatically changes the source or handle position, we need to update the internals
|
||||
* to make sure the edges are updated correctly
|
||||
*/
|
||||
const typeChanged = prevType.current !== nodeType;
|
||||
const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
||||
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
||||
|
||||
@@ -23,9 +23,9 @@ export const builtinNodeTypes: NodeTypes = {
|
||||
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
||||
node: InternalNode<NodeType>
|
||||
): {
|
||||
width: number | string | undefined;
|
||||
height: number | string | undefined;
|
||||
} {
|
||||
width: number | string | undefined;
|
||||
height: number | string | undefined;
|
||||
} {
|
||||
if (node.internals.handleBounds === undefined) {
|
||||
return {
|
||||
width: node.width ?? node.initialWidth ?? node.style?.width,
|
||||
|
||||
@@ -4,10 +4,12 @@ import { errorMessages } from '@xyflow/system';
|
||||
|
||||
import type { ReactFlowState } from '../../types';
|
||||
|
||||
// this handler is called by
|
||||
// 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
||||
// or
|
||||
// 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
||||
/*
|
||||
* this handler is called by
|
||||
* 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
||||
* or
|
||||
* 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
||||
*/
|
||||
export function handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
|
||||
@@ -61,9 +61,9 @@ export function NodesSelection<NodeType extends Node>({
|
||||
|
||||
const onContextMenu = onSelectionContextMenu
|
||||
? (event: MouseEvent) => {
|
||||
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
||||
onSelectionContextMenu(event, selectedNodes);
|
||||
}
|
||||
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
||||
onSelectionContextMenu(event, selectedNodes);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';
|
||||
import { HTMLAttributes, forwardRef } from 'react';
|
||||
import cc from 'classcat';
|
||||
import type { PanelPosition } from '@xyflow/system';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
||||
/** Set position of the panel
|
||||
* @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||||
/**
|
||||
* The position of the panel.
|
||||
* @default "top-left"
|
||||
*/
|
||||
position?: PanelPosition;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all');
|
||||
|
||||
/**
|
||||
* The `<Panel />` component helps you position content above the viewport.
|
||||
* It is used internally by the [`<MiniMap />`](/api-reference/components/minimap)
|
||||
* and [`<Controls />`](/api-reference/components/controls) components.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { ReactFlow, Background, Panel } from '@xyflow/react';
|
||||
*
|
||||
*export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlow nodes={[]} fitView>
|
||||
* <Panel position="top-left">top-left</Panel>
|
||||
* <Panel position="top-center">top-center</Panel>
|
||||
* <Panel position="top-right">top-right</Panel>
|
||||
* <Panel position="bottom-left">bottom-left</Panel>
|
||||
* <Panel position="bottom-center">bottom-center</Panel>
|
||||
* <Panel position="bottom-right">bottom-right</Panel>
|
||||
* </ReactFlow>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const Panel = forwardRef<HTMLDivElement, PanelProps>(
|
||||
({ position = 'top-left', children, className, style, ...rest }, ref) => {
|
||||
const pointerEvents = useStore(selector);
|
||||
@@ -32,3 +60,5 @@ export const Panel = forwardRef<HTMLDivElement, PanelProps>(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Panel.displayName = 'Panel';
|
||||
|
||||
@@ -19,6 +19,40 @@ export type ReactFlowProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `<ReactFlowProvider />` component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context#)
|
||||
* that makes it possible to access a flow's internal state outside of the
|
||||
* [`<ReactFlow />`](/api-reference/react-flow) component. Many of the hooks we
|
||||
* provide rely on this component to work.
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { ReactFlow, ReactFlowProvider, useNodes } from '@xyflow/react'
|
||||
*
|
||||
*export default function Flow() {
|
||||
* return (
|
||||
* <ReactFlowProvider>
|
||||
* <ReactFlow nodes={...} edges={...} />
|
||||
* <Sidebar />
|
||||
* </ReactFlowProvider>
|
||||
* );
|
||||
*}
|
||||
*
|
||||
*function Sidebar() {
|
||||
* // This hook will only work if the component it's used in is a child of a
|
||||
* // <ReactFlowProvider />.
|
||||
* const nodes = useNodes()
|
||||
*
|
||||
* return <aside>do something with nodes</aside>;
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks If you're using a router and want your flow's state to persist across routes,
|
||||
* it's vital that you place the `<ReactFlowProvider />` component _outside_ of
|
||||
* your router. If you have multiple flows on the same page you will need to use a separate
|
||||
* `<ReactFlowProvider />` for each flow.
|
||||
*/
|
||||
export function ReactFlowProvider({
|
||||
initialNodes: nodes,
|
||||
initialEdges: edges,
|
||||
|
||||
@@ -10,8 +10,8 @@ import { shallow } from 'zustand/shallow';
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import type { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types';
|
||||
|
||||
type SelectionListenerProps = {
|
||||
onSelectionChange?: OnSelectionChangeFunc;
|
||||
type SelectionListenerProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
onSelectionChange?: OnSelectionChangeFunc<NodeType, EdgeType>;
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => {
|
||||
@@ -44,12 +44,14 @@ function areEqual(a: SelectorSlice, b: SelectorSlice) {
|
||||
);
|
||||
}
|
||||
|
||||
function SelectionListenerInner({ onSelectionChange }: SelectionListenerProps) {
|
||||
const store = useStoreApi();
|
||||
function SelectionListenerInner<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
|
||||
onSelectionChange,
|
||||
}: SelectionListenerProps<NodeType, EdgeType>) {
|
||||
const store = useStoreApi<NodeType, EdgeType>();
|
||||
const { selectedNodes, selectedEdges } = useStore(selector, areEqual);
|
||||
|
||||
useEffect(() => {
|
||||
const params = { nodes: selectedNodes, edges: selectedEdges };
|
||||
const params = { nodes: selectedNodes as NodeType[], edges: selectedEdges as EdgeType[] };
|
||||
|
||||
onSelectionChange?.(params);
|
||||
store.getState().onSelectionChangeHandlers.forEach((fn) => fn(params));
|
||||
@@ -60,11 +62,13 @@ function SelectionListenerInner({ onSelectionChange }: SelectionListenerProps) {
|
||||
|
||||
const changeSelector = (s: ReactFlowState) => !!s.onSelectionChangeHandlers;
|
||||
|
||||
export function SelectionListener({ onSelectionChange }: SelectionListenerProps) {
|
||||
export function SelectionListener<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
|
||||
onSelectionChange,
|
||||
}: SelectionListenerProps<NodeType, EdgeType>) {
|
||||
const storeHasSelectionChangeHandlers = useStore(changeSelector);
|
||||
|
||||
if (onSelectionChange || storeHasSelectionChangeHandlers) {
|
||||
return <SelectionListenerInner onSelectionChange={onSelectionChange} />;
|
||||
return <SelectionListenerInner<NodeType, EdgeType> onSelectionChange={onSelectionChange} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types';
|
||||
import { defaultNodeOrigin } from '../../container/ReactFlow/init-values';
|
||||
|
||||
// these fields exist in the global store and we need to keep them up to date
|
||||
// These fields exist in the global store, and we need to keep them up to date
|
||||
const reactFlowFieldsToTrack = [
|
||||
'nodes',
|
||||
'edges',
|
||||
@@ -94,9 +94,11 @@ const selector = (s: ReactFlowState) => ({
|
||||
});
|
||||
|
||||
const initPrevValues = {
|
||||
// these are values that are also passed directly to other components
|
||||
// than the StoreUpdater. We can reduce the number of setStore calls
|
||||
// by setting the same values here as prev fields.
|
||||
/*
|
||||
* these are values that are also passed directly to other components
|
||||
* than the StoreUpdater. We can reduce the number of setStore calls
|
||||
* by setting the same values here as prev fields.
|
||||
*/
|
||||
translateExtent: infiniteExtent,
|
||||
nodeOrigin: defaultNodeOrigin,
|
||||
minZoom: 0.5,
|
||||
@@ -152,8 +154,8 @@ export function StoreUpdater<NodeType extends Node = Node, EdgeType extends Edge
|
||||
else if (fieldName === 'nodeExtent') setNodeExtent(fieldValue as CoordinateExtent);
|
||||
else if (fieldName === 'paneClickDistance') setPaneClickDistance(fieldValue as number);
|
||||
// Renamed fields
|
||||
else if (fieldName === 'fitView') store.setState({ fitViewOnInit: fieldValue as boolean });
|
||||
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOnInitOptions: fieldValue as FitViewOptions });
|
||||
else if (fieldName === 'fitView') store.setState({ fitViewQueued: fieldValue as boolean });
|
||||
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOptions: fieldValue as FitViewOptions });
|
||||
// General case
|
||||
else store.setState({ [fieldName]: fieldValue });
|
||||
}
|
||||
|
||||
@@ -6,6 +6,31 @@ import type { ReactFlowState } from '../../types';
|
||||
|
||||
const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__viewport-portal');
|
||||
|
||||
/**
|
||||
* The `<ViewportPortal />` component can be used to add components to the same viewport
|
||||
* of the flow where nodes and edges are rendered. This is useful when you want to render
|
||||
* your own components that are adhere to the same coordinate system as the nodes & edges
|
||||
* and are also affected by zooming and panning
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```jsx
|
||||
*import React from 'react';
|
||||
*import { ViewportPortal } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* return (
|
||||
* <ViewportPortal>
|
||||
* <div
|
||||
* style={{ transform: 'translate(100px, 100px)', position: 'absolute' }}
|
||||
* >
|
||||
* This div is positioned at [100, 100] on the flow.
|
||||
* </div>
|
||||
* </ViewportPortal>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function ViewportPortal({ children }: { children: ReactNode }) {
|
||||
const viewPortalDiv = useStore(selector);
|
||||
|
||||
|
||||
@@ -42,9 +42,11 @@ const Marker = ({
|
||||
);
|
||||
};
|
||||
|
||||
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
||||
// that we can then use for creating our unique marker ids
|
||||
/*
|
||||
* when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||
* when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
||||
* that we can then use for creating our unique marker ids
|
||||
*/
|
||||
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
||||
const edges = useStore((s) => s.edges);
|
||||
const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
|
||||
|
||||
@@ -170,7 +170,7 @@ function GraphViewComponent<NodeType extends Node = Node, EdgeType extends Edge
|
||||
disableKeyboardA11y={disableKeyboardA11y}
|
||||
rfId={rfId}
|
||||
/>
|
||||
<ConnectionLineWrapper
|
||||
<ConnectionLineWrapper<NodeType>
|
||||
style={connectionLineStyle}
|
||||
type={connectionLineType}
|
||||
component={connectionLineComponent}
|
||||
|
||||
@@ -44,29 +44,31 @@ function NodeRendererComponent<NodeType extends Node>(props: NodeRendererProps<N
|
||||
<div className="react-flow__nodes" style={containerStyle}>
|
||||
{nodeIds.map((nodeId) => {
|
||||
return (
|
||||
// The split of responsibilities between NodeRenderer and
|
||||
// NodeComponentWrapper may appear weird. However, it’s designed to
|
||||
// minimize the cost of updates when individual nodes change.
|
||||
//
|
||||
// For example, when you’re dragging a single node, that node gets
|
||||
// updated multiple times per second. If `NodeRenderer` were to update
|
||||
// every time, it would have to re-run the `nodes.map()` loop every
|
||||
// time. This gets pricey with hundreds of nodes, especially if every
|
||||
// loop cycle does more than just rendering a JSX element!
|
||||
//
|
||||
// As a result of this choice, we took the following implementation
|
||||
// decisions:
|
||||
// - NodeRenderer subscribes *only* to node IDs – and therefore
|
||||
// rerender *only* when visible nodes are added or removed.
|
||||
// - NodeRenderer performs all operations the result of which can be
|
||||
// shared between nodes (such as creating the `ResizeObserver`
|
||||
// instance, or subscribing to `selector`). This means extra prop
|
||||
// drilling into `NodeComponentWrapper`, but it means we need to run
|
||||
// these operations only once – instead of once per node.
|
||||
// - Any operations that you’d normally write inside `nodes.map` are
|
||||
// moved into `NodeComponentWrapper`. This ensures they are
|
||||
// memorized – so if `NodeRenderer` *has* to rerender, it only
|
||||
// needs to regenerate the list of nodes, nothing else.
|
||||
/*
|
||||
* The split of responsibilities between NodeRenderer and
|
||||
* NodeComponentWrapper may appear weird. However, it’s designed to
|
||||
* minimize the cost of updates when individual nodes change.
|
||||
*
|
||||
* For example, when you’re dragging a single node, that node gets
|
||||
* updated multiple times per second. If `NodeRenderer` were to update
|
||||
* every time, it would have to re-run the `nodes.map()` loop every
|
||||
* time. This gets pricey with hundreds of nodes, especially if every
|
||||
* loop cycle does more than just rendering a JSX element!
|
||||
*
|
||||
* As a result of this choice, we took the following implementation
|
||||
* decisions:
|
||||
* - NodeRenderer subscribes *only* to node IDs – and therefore
|
||||
* rerender *only* when visible nodes are added or removed.
|
||||
* - NodeRenderer performs all operations the result of which can be
|
||||
* shared between nodes (such as creating the `ResizeObserver`
|
||||
* instance, or subscribing to `selector`). This means extra prop
|
||||
* drilling into `NodeComponentWrapper`, but it means we need to run
|
||||
* these operations only once – instead of once per node.
|
||||
* - Any operations that you’d normally write inside `nodes.map` are
|
||||
* moved into `NodeComponentWrapper`. This ensures they are
|
||||
* memorized – so if `NodeRenderer` *has* to rerender, it only
|
||||
* needs to regenerate the list of nodes, nothing else.
|
||||
*/
|
||||
<NodeWrapper<NodeType>
|
||||
key={nodeId}
|
||||
id={nodeId}
|
||||
|
||||
@@ -61,6 +61,7 @@ const wrapHandler = (
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
userSelectionActive: s.userSelectionActive,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
connectionInProgress: s.connection.inProgress,
|
||||
dragging: s.paneDragging,
|
||||
});
|
||||
|
||||
@@ -81,7 +82,7 @@ export function Pane({
|
||||
children,
|
||||
}: PaneProps) {
|
||||
const store = useStoreApi();
|
||||
const { userSelectionActive, elementsSelectable, dragging } = useStore(selector, shallow);
|
||||
const { userSelectionActive, elementsSelectable, dragging, connectionInProgress } = useStore(selector, shallow);
|
||||
const hasActiveSelection = elementsSelectable && (isSelecting || userSelectionActive);
|
||||
|
||||
const container = useRef<HTMLDivElement | null>(null);
|
||||
@@ -96,7 +97,8 @@ export function Pane({
|
||||
|
||||
const onClick = (event: ReactMouseEvent) => {
|
||||
// We prevent click events when the user let go of the selectionKey during a selection
|
||||
if (selectionInProgress.current) {
|
||||
// We also prevent click events when a connection is in progress
|
||||
if (selectionInProgress.current || connectionInProgress) {
|
||||
selectionInProgress.current = false;
|
||||
return;
|
||||
}
|
||||
@@ -233,8 +235,10 @@ export function Pane({
|
||||
(event.target as Partial<Element>)?.releasePointerCapture?.(event.pointerId);
|
||||
const { userSelectionRect } = store.getState();
|
||||
|
||||
// We only want to trigger click functions when in selection mode if
|
||||
// the user did not move the mouse.
|
||||
/*
|
||||
* We only want to trigger click functions when in selection mode if
|
||||
* the user did not move the mouse.
|
||||
*/
|
||||
if (!userSelectionActive && userSelectionRect && event.target === container.current) {
|
||||
onClick?.(event);
|
||||
}
|
||||
@@ -246,8 +250,10 @@ export function Pane({
|
||||
});
|
||||
onSelectionEnd?.(event);
|
||||
|
||||
// If the user kept holding the selectionKey during the selection,
|
||||
// we need to reset the selectionInProgress, so the next click event is not prevented
|
||||
/*
|
||||
* If the user kept holding the selectionKey during the selection,
|
||||
* we need to reset the selectionInProgress, so the next click event is not prevented
|
||||
*/
|
||||
if (selectionKeyPressed || selectionOnDrag) {
|
||||
selectionInProgress.current = false;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@ export function Wrapper({
|
||||
const isWrapped = useContext(StoreContext);
|
||||
|
||||
if (isWrapped) {
|
||||
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
||||
/*
|
||||
* we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
||||
* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
||||
*/
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ForwardedRef, type CSSProperties } from 'react';
|
||||
import { ForwardedRef, useCallback, type CSSProperties } from 'react';
|
||||
import cc from 'classcat';
|
||||
import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system';
|
||||
|
||||
@@ -144,6 +144,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
height,
|
||||
colorMode = 'light',
|
||||
debug,
|
||||
onScroll,
|
||||
...rest
|
||||
}: ReactFlowProps<NodeType, EdgeType>,
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
@@ -151,10 +152,20 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
const rfId = id || '1';
|
||||
const colorModeClassName = useColorModeClass(colorMode);
|
||||
|
||||
// Undo scroll events, preventing viewport from shifting when nodes outside of it are focused
|
||||
const wrapperOnScroll = useCallback(
|
||||
(e: React.UIEvent<HTMLDivElement>) => {
|
||||
e.currentTarget.scrollTo({ top: 0, left: 0, behavior: 'instant' });
|
||||
onScroll?.(e);
|
||||
},
|
||||
[onScroll]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="rf__wrapper"
|
||||
{...rest}
|
||||
onScroll={wrapperOnScroll}
|
||||
style={{ ...style, ...wrapperStyle }}
|
||||
ref={ref}
|
||||
className={cc(['react-flow', className, colorModeClassName])}
|
||||
@@ -292,7 +303,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
paneClickDistance={paneClickDistance}
|
||||
debug={debug}
|
||||
/>
|
||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||
<SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
<Attribution proOptions={proOptions} position={attributionPosition} />
|
||||
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} />
|
||||
@@ -301,4 +312,24 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The `<ReactFlow />` component is the heart of your React Flow application.
|
||||
* It renders your nodes and edges and handles user interaction
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { ReactFlow } from '@xyflow/react'
|
||||
*
|
||||
*export default function Flow() {
|
||||
* return (<ReactFlow
|
||||
* nodes={...}
|
||||
* edges={...}
|
||||
* onNodesChange={...}
|
||||
* ...
|
||||
* />);
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export default fixedForwardRef(ReactFlow);
|
||||
|
||||
@@ -95,7 +95,7 @@ export function ZoomPane({
|
||||
},
|
||||
});
|
||||
|
||||
const { x, y, zoom } = panZoom.current!.getViewport();
|
||||
const { x, y, zoom } = panZoom.current.getViewport();
|
||||
|
||||
store.setState({
|
||||
panZoom: panZoom.current,
|
||||
|
||||
@@ -4,6 +4,34 @@ export const NodeIdContext = createContext<string | null>(null);
|
||||
export const Provider = NodeIdContext.Provider;
|
||||
export const Consumer = NodeIdContext.Consumer;
|
||||
|
||||
/**
|
||||
* You can use this hook to get the id of the node it is used inside. It is useful
|
||||
* if you need the node's id deeper in the render tree but don't want to manually
|
||||
* drill down the id as a prop.
|
||||
*
|
||||
* @public
|
||||
* @returns The id for a node in the flow.
|
||||
*
|
||||
* @example
|
||||
*```jsx
|
||||
*import { useNodeId } from '@xyflow/react';
|
||||
*
|
||||
*export default function CustomNode() {
|
||||
* return (
|
||||
* <div>
|
||||
* <span>This node has an id of </span>
|
||||
* <NodeIdDisplay />
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*
|
||||
*function NodeIdDisplay() {
|
||||
* const nodeId = useNodeId();
|
||||
*
|
||||
* return <span>{nodeId}</span>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const useNodeId = (): string | null => {
|
||||
const nodeId = useContext(NodeIdContext);
|
||||
return nodeId;
|
||||
|
||||
@@ -24,9 +24,32 @@ function getSelector<NodeType extends Node = Node, SelectorReturn = ConnectionSt
|
||||
return storeSelector;
|
||||
}
|
||||
/**
|
||||
* Hook for accessing the connection state.
|
||||
* The `useConnection` hook returns the current connection when there is an active
|
||||
* connection interaction. If no connection interaction is active, it returns null
|
||||
* for every property. A typical use case for this hook is to colorize handles
|
||||
* based on a certain condition (e.g. if the connection is valid or not).
|
||||
*
|
||||
* @public
|
||||
* @param connectionSelector - An optional selector function used to extract a slice of the
|
||||
* `ConnectionState` data. Using a selector can prevent component re-renders where data you don't
|
||||
* otherwise care about might change. If a selector is not provided, the entire `ConnectionState`
|
||||
* object is returned unchanged.
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
*import { useConnection } from '@xyflow/react';
|
||||
*
|
||||
*function App() {
|
||||
* const connection = useConnection();
|
||||
*
|
||||
* return (
|
||||
* <div> {connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'}
|
||||
*
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns ConnectionState
|
||||
*/
|
||||
export function useConnection<NodeType extends Node = Node, SelectorReturn = ConnectionState<InternalNode<NodeType>>>(
|
||||
|
||||
@@ -6,10 +6,22 @@ import type { Edge, ReactFlowState } from '../types';
|
||||
const edgesSelector = (state: ReactFlowState) => state.edges;
|
||||
|
||||
/**
|
||||
* Hook for getting the current edges from the store.
|
||||
* This hook returns an array of the current edges. Components that use this hook
|
||||
* will re-render **whenever any edge changes**.
|
||||
*
|
||||
* @public
|
||||
* @returns An array of edges
|
||||
* @returns An array of all edges currently in the flow.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useEdges } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const edges = useEdges();
|
||||
*
|
||||
* return <div>There are currently {edges.length} edges!</div>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useEdges<EdgeType extends Edge = Edge>(): EdgeType[] {
|
||||
const edges = useStore(edgesSelector, shallow) as EdgeType[];
|
||||
|
||||
@@ -10,11 +10,16 @@ import {
|
||||
import { useStore } from './useStore';
|
||||
import { useNodeId } from '../contexts/NodeIdContext';
|
||||
|
||||
type useHandleConnectionsParams = {
|
||||
type UseHandleConnectionsParams = {
|
||||
/** What type of handle connections do you want to observe? */
|
||||
type: HandleType;
|
||||
/** The handle id (this is only needed if the node has multiple handles of the same type). */
|
||||
id?: string | null;
|
||||
/** If node id is not provided, the node id from the `NodeIdContext` is used. */
|
||||
nodeId?: string;
|
||||
/** Gets called when a connection is established. */
|
||||
onConnect?: (connections: Connection[]) => void;
|
||||
/** Gets called when a connection is removed. */
|
||||
onDisconnect?: (connections: Connection[]) => void;
|
||||
};
|
||||
|
||||
@@ -23,12 +28,7 @@ type useHandleConnectionsParams = {
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use `useNodeConnections` instead.
|
||||
* @param param.type - handle type 'source' or 'target'
|
||||
* @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used
|
||||
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
|
||||
* @param param.onConnect - gets called when a connection is established
|
||||
* @param param.onDisconnect - gets called when a connection is removed
|
||||
* @returns an array with handle connections
|
||||
* @returns An array with handle connections.
|
||||
*/
|
||||
export function useHandleConnections({
|
||||
type,
|
||||
@@ -36,7 +36,7 @@ export function useHandleConnections({
|
||||
nodeId,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
}: useHandleConnectionsParams): HandleConnection[] {
|
||||
}: UseHandleConnectionsParams): HandleConnection[] {
|
||||
console.warn(
|
||||
'[DEPRECATED] `useHandleConnections` is deprecated. Instead use `useNodeConnections` https://reactflow.dev/api-reference/hooks/useNodeConnections'
|
||||
);
|
||||
|
||||
@@ -5,11 +5,31 @@ import { useStore } from './useStore';
|
||||
import type { InternalNode, Node } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for getting an internal node by id
|
||||
* This hook returns the internal representation of a specific node.
|
||||
* Components that use this hook will re-render **whenever the node changes**,
|
||||
* including when a node is selected or moved.
|
||||
*
|
||||
* @public
|
||||
* @param id - id of the node
|
||||
* @returns array with visible node ids
|
||||
* @param id - The ID of a node you want to observe.
|
||||
* @returns The `InternalNode` object for the node with the given ID.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useInternalNode } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const internalNode = useInternalNode('node-1');
|
||||
* const absolutePosition = internalNode.internals.positionAbsolute;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* The absolute position of the node is at:
|
||||
* <p>x: {absolutePosition.x}</p>
|
||||
* <p>y: {absolutePosition.y}</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useInternalNode<NodeType extends Node = Node>(id: string): InternalNode<NodeType> | undefined {
|
||||
const node = useStore(
|
||||
|
||||
@@ -6,25 +6,58 @@ type PressedKeys = Set<string>;
|
||||
type KeyOrCode = 'key' | 'code';
|
||||
|
||||
export type UseKeyPressOptions = {
|
||||
/**
|
||||
* Listen to key presses on a specific element.
|
||||
* @default document
|
||||
*/
|
||||
target?: Window | Document | HTMLElement | ShadowRoot | null;
|
||||
/**
|
||||
* You can use this flag to prevent triggering the key press hook when an input field is focused.
|
||||
* @default true
|
||||
*/
|
||||
actInsideInputWithModifier?: boolean;
|
||||
preventDefault?: boolean;
|
||||
};
|
||||
|
||||
const defaultDoc = typeof document !== 'undefined' ? document : null;
|
||||
|
||||
/**
|
||||
* Hook for handling key events.
|
||||
* This hook lets you listen for specific key codes and tells you whether they are
|
||||
* currently pressed or not.
|
||||
*
|
||||
* @public
|
||||
* @param param.keyCode - The key code (string or array of strings) to use
|
||||
* @param param.options - Options
|
||||
* @returns boolean
|
||||
* @param options - Options
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useKeyPress } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const spacePressed = useKeyPress('Space');
|
||||
* const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {spacePressed && <p>Space pressed!</p>}
|
||||
* {cmdAndSPressed && <p>Cmd + S pressed!</p>}
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useKeyPress(
|
||||
// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
||||
// a string means a single key 'a' or a combination when '+' is used 'a+d'
|
||||
// an array means different possibilites. Explainer: ['a', 'd+s'] here the
|
||||
// user can use the single key 'a' or the combination 'd' + 's'
|
||||
/**
|
||||
* The key code (string or array of strings) specifies which key(s) should trigger
|
||||
* an action.
|
||||
*
|
||||
* A **string** can represent:
|
||||
* - A **single key**, e.g. `'a'`
|
||||
* - A **key combination**, using `'+'` to separate keys, e.g. `'a+d'`
|
||||
*
|
||||
* An **array of strings** represents **multiple possible key inputs**. For example, `['a', 'd+s']`
|
||||
* means the user can press either the single key `'a'` or the combination of `'d'` and `'s'`.
|
||||
* @default null
|
||||
*/
|
||||
keyCode: KeyCode | null = null,
|
||||
options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true }
|
||||
): boolean {
|
||||
@@ -36,20 +69,24 @@ export function useKeyPress(
|
||||
// we need to remember the pressed keys in order to support combinations
|
||||
const pressedKeys = useRef<PressedKeys>(new Set([]));
|
||||
|
||||
// keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
// keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
// used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
// we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
// and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
// we can't find it in the list of keysToWatch.
|
||||
/*
|
||||
* keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
* keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
* used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
* we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
* and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
* we can't find it in the list of keysToWatch.
|
||||
*/
|
||||
const [keyCodes, keysToWatch] = useMemo<[Array<Keys>, Keys]>(() => {
|
||||
if (keyCode !== null) {
|
||||
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
||||
const keys = keyCodeArr
|
||||
.filter((kc) => typeof kc === 'string')
|
||||
// we first replace all '+' with '\n' which we will use to split the keys on
|
||||
// then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
// in the end we simply split on '\n' to get the key array
|
||||
/*
|
||||
* we first replace all '+' with '\n' which we will use to split the keys on
|
||||
* then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
* in the end we simply split on '\n' to get the key array
|
||||
*/
|
||||
.map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n'));
|
||||
const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
|
||||
|
||||
@@ -64,7 +101,7 @@ export function useKeyPress(
|
||||
|
||||
if (keyCode !== null) {
|
||||
const downHandler = (event: KeyboardEvent) => {
|
||||
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey || event.altKey;
|
||||
const preventAction =
|
||||
(!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
||||
isInputDOMNode(event);
|
||||
@@ -76,19 +113,18 @@ export function useKeyPress(
|
||||
pressedKeys.current.add(event[keyOrCode]);
|
||||
|
||||
if (isMatchingKey(keyCodes, pressedKeys.current, false)) {
|
||||
event.preventDefault();
|
||||
const target = (event.composedPath?.()?.[0] || event.target) as Element | null;
|
||||
const isInteractiveElement = target?.nodeName === 'BUTTON' || target?.nodeName === 'A';
|
||||
|
||||
if (options.preventDefault !== false && (modifierPressed.current || !isInteractiveElement)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
setKeyPressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = (event: KeyboardEvent) => {
|
||||
const preventAction =
|
||||
(!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
||||
isInputDOMNode(event);
|
||||
|
||||
if (preventAction) {
|
||||
return false;
|
||||
}
|
||||
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
|
||||
|
||||
if (isMatchingKey(keyCodes, pressedKeys.current, true)) {
|
||||
@@ -133,12 +169,16 @@ export function useKeyPress(
|
||||
function isMatchingKey(keyCodes: Array<Keys>, pressedKeys: PressedKeys, isUp: boolean): boolean {
|
||||
return (
|
||||
keyCodes
|
||||
// we only want to compare same sizes of keyCode definitions
|
||||
// and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
// this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
/*
|
||||
* we only want to compare same sizes of keyCode definitions
|
||||
* and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
* this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
*/
|
||||
.filter((keys) => isUp || keys.length === pressedKeys.size)
|
||||
// since we want to support multiple possibilities only one of the
|
||||
// combinations need to be part of the pressed keys
|
||||
/*
|
||||
* since we want to support multiple possibilities only one of the
|
||||
* combinations need to be part of the pressed keys
|
||||
*/
|
||||
.some((keys) => keys.every((k) => pressedKeys.has(k)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ export function useMoveSelectedNodes() {
|
||||
const nodeUpdates = new Map();
|
||||
const isSelected = selectedAndDraggable(nodesDraggable);
|
||||
|
||||
// by default a node moves 5px on each key press
|
||||
// if snap grid is enabled, we use that for the velocity
|
||||
/*
|
||||
* by default a node moves 5px on each key press
|
||||
* if snap grid is enabled, we use that for the velocity
|
||||
*/
|
||||
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
||||
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
||||
|
||||
|
||||
@@ -14,23 +14,39 @@ import { useNodeId } from '../contexts/NodeIdContext';
|
||||
const error014 = errorMessages['error014']();
|
||||
|
||||
type UseNodeConnectionsParams = {
|
||||
/** ID of the node, filled in automatically if used inside custom node. */
|
||||
id?: string;
|
||||
/** What type of handle connections do you want to observe? */
|
||||
handleType?: HandleType;
|
||||
/** Filter by handle id (this is only needed if the node has multiple handles of the same type). */
|
||||
handleId?: string;
|
||||
/** Gets called when a connection is established. */
|
||||
onConnect?: (connections: Connection[]) => void;
|
||||
/** Gets called when a connection is removed. */
|
||||
onDisconnect?: (connections: Connection[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to retrieve all edges connected to a node. Can be filtered by handle type and id.
|
||||
* This hook returns an array of connections on a specific node, handle type ('source', 'target') or handle ID.
|
||||
*
|
||||
* @public
|
||||
* @param param.id - node id - optional if called inside a custom node
|
||||
* @param param.handleType - filter by handle type 'source' or 'target'
|
||||
* @param param.handleId - filter by handle id (this is only needed if the node has multiple handles of the same type)
|
||||
* @param param.onConnect - gets called when a connection is established
|
||||
* @param param.onDisconnect - gets called when a connection is removed
|
||||
* @returns an array with connections
|
||||
* @returns An array with connections.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useNodeConnections } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const connections = useNodeConnections({
|
||||
* handleType: 'target',
|
||||
* handleId: 'my-handle',
|
||||
* });
|
||||
*
|
||||
* return (
|
||||
* <div>There are currently {connections.length} incoming connections!</div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodeConnections({
|
||||
id,
|
||||
@@ -57,7 +73,7 @@ export function useNodeConnections({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
||||
// @todo discuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
||||
if (prevConnections.current && prevConnections.current !== connections) {
|
||||
const _connections = connections ?? new Map();
|
||||
handleConnectionChange(prevConnections.current, _connections, onDisconnect);
|
||||
|
||||
@@ -6,10 +6,23 @@ import type { Node, ReactFlowState } from '../types';
|
||||
const nodesSelector = (state: ReactFlowState) => state.nodes;
|
||||
|
||||
/**
|
||||
* Hook for getting the current nodes from the store.
|
||||
* This hook returns an array of the current nodes. Components that use this hook
|
||||
* will re-render **whenever any node changes**, including when a node is selected
|
||||
* or moved.
|
||||
*
|
||||
* @public
|
||||
* @returns An array of nodes
|
||||
* @returns An array of all nodes currently in the flow.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useNodes } from '@xyflow/react';
|
||||
*
|
||||
*export default function() {
|
||||
* const nodes = useNodes();
|
||||
*
|
||||
* return <div>There are currently {nodes.length} nodes!</div>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodes<NodeType extends Node = Node>(): NodeType[] {
|
||||
const nodes = useStore(nodesSelector, shallow) as NodeType[];
|
||||
|
||||
@@ -5,17 +5,31 @@ import { useStore } from '../hooks/useStore';
|
||||
import type { Node } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for receiving data of one or multiple nodes
|
||||
* This hook lets you subscribe to changes of a specific nodes `data` object.
|
||||
*
|
||||
* @public
|
||||
* @param nodeId - The id (or ids) of the node to get the data from
|
||||
* @param guard - Optional guard function to narrow down the node type
|
||||
* @returns An object (or array of object) with {id, type, data} representing each node
|
||||
* @returns An object (or array of object) with `id`, `type`, `data` representing each node.
|
||||
*
|
||||
* @example
|
||||
*```jsx
|
||||
*import { useNodesData } from '@xyflow/react';
|
||||
*
|
||||
*export default function() {
|
||||
* const nodeData = useNodesData('nodeId-1');
|
||||
* const nodesData = useNodesData(['nodeId-1', 'nodeId-2']);
|
||||
*
|
||||
* return null;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodesData<NodeType extends Node = Node>(
|
||||
/** The id of the node to get the data from. */
|
||||
nodeId: string
|
||||
): Pick<NodeType, 'id' | 'type' | 'data'> | null;
|
||||
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): Pick<NodeType, 'id' | 'type' | 'data'>[];
|
||||
export function useNodesData<NodeType extends Node = Node>(
|
||||
/** The ids of the nodes to get the data from. */
|
||||
nodeIds: string[]
|
||||
): Pick<NodeType, 'id' | 'type' | 'data'>[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function useNodesData(nodeIds: any): any {
|
||||
const nodesData = useStore(
|
||||
|
||||
@@ -4,15 +4,58 @@ import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
||||
import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
|
||||
* This hook makes it easy to prototype a controlled flow where you manage the
|
||||
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
||||
* like React's `useState` hook with an additional helper callback.
|
||||
*
|
||||
* @public
|
||||
* @param initialNodes
|
||||
* @returns an array [nodes, setNodes, onNodesChange]
|
||||
* @returns
|
||||
* - `nodes`: The current array of nodes. You might pass this directly to the `nodes` prop of your
|
||||
* `<ReactFlow />` component, or you may want to manipulate it first to perform some layouting,
|
||||
* for example.
|
||||
* - `setNodes`: A function that you can use to update the nodes. You can pass it a new array of
|
||||
* nodes or a callback that receives the current array of nodes and returns a new array of nodes.
|
||||
* This is the same as the second element of the tuple returned by React's `useState` hook.
|
||||
* - `onNodesChange`: A handy callback that can take an array of `NodeChanges` and update the nodes
|
||||
* state accordingly. You'll typically pass this directly to the `onNodesChange` prop of your
|
||||
* `<ReactFlow />` component.
|
||||
* @example
|
||||
*
|
||||
*```tsx
|
||||
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
||||
*
|
||||
*const initialNodes = [];
|
||||
*const initialEdges = [];
|
||||
*
|
||||
*export default function () {
|
||||
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow
|
||||
* nodes={nodes}
|
||||
* edges={edges}
|
||||
* onNodesChange={onNodesChange}
|
||||
* onEdgesChange={onEdgesChange}
|
||||
* />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook was created to make prototyping easier and our documentation
|
||||
* examples clearer. Although it is OK to use this hook in production, in
|
||||
* practice you may want to use a more sophisticated state management solution
|
||||
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
||||
*
|
||||
*/
|
||||
export function useNodesState<NodeType extends Node>(
|
||||
initialNodes: NodeType[]
|
||||
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange<NodeType>] {
|
||||
): [
|
||||
//
|
||||
nodes: NodeType[],
|
||||
setNodes: Dispatch<SetStateAction<NodeType[]>>,
|
||||
onNodesChange: OnNodesChange<NodeType>
|
||||
] {
|
||||
const [nodes, setNodes] = useState(initialNodes);
|
||||
const onNodesChange: OnNodesChange<NodeType> = useCallback(
|
||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
@@ -23,15 +66,60 @@ export function useNodesState<NodeType extends Node>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
|
||||
* This hook makes it easy to prototype a controlled flow where you manage the
|
||||
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
||||
* like React's `useState` hook with an additional helper callback.
|
||||
*
|
||||
* @public
|
||||
* @param initialEdges
|
||||
* @returns an array [edges, setEdges, onEdgesChange]
|
||||
* @returns
|
||||
* - `edges`: The current array of edges. You might pass this directly to the `edges` prop of your
|
||||
* `<ReactFlow />` component, or you may want to manipulate it first to perform some layouting,
|
||||
* for example.
|
||||
*
|
||||
* - `setEdges`: A function that you can use to update the edges. You can pass it a new array of
|
||||
* edges or a callback that receives the current array of edges and returns a new array of edges.
|
||||
* This is the same as the second element of the tuple returned by React's `useState` hook.
|
||||
*
|
||||
* - `onEdgesChange`: A handy callback that can take an array of `EdgeChanges` and update the edges
|
||||
* state accordingly. You'll typically pass this directly to the `onEdgesChange` prop of your
|
||||
* `<ReactFlow />` component.
|
||||
* @example
|
||||
*
|
||||
*```tsx
|
||||
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
||||
*
|
||||
*const initialNodes = [];
|
||||
*const initialEdges = [];
|
||||
*
|
||||
*export default function () {
|
||||
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow
|
||||
* nodes={nodes}
|
||||
* edges={edges}
|
||||
* onNodesChange={onNodesChange}
|
||||
* onEdgesChange={onEdgesChange}
|
||||
* />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook was created to make prototyping easier and our documentation
|
||||
* examples clearer. Although it is OK to use this hook in production, in
|
||||
* practice you may want to use a more sophisticated state management solution
|
||||
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
||||
*
|
||||
*/
|
||||
export function useEdgesState<EdgeType extends Edge = Edge>(
|
||||
initialEdges: EdgeType[]
|
||||
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange<EdgeType>] {
|
||||
): [
|
||||
//
|
||||
edges: EdgeType[],
|
||||
setEdges: Dispatch<SetStateAction<EdgeType[]>>,
|
||||
onEdgesChange: OnEdgesChange<EdgeType>
|
||||
] {
|
||||
const [edges, setEdges] = useState(initialEdges);
|
||||
const onEdgesChange: OnEdgesChange<EdgeType> = useCallback(
|
||||
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useStore } from './useStore';
|
||||
import type { ReactFlowState } from '../types';
|
||||
import { nodeHasDimensions } from '@xyflow/system';
|
||||
|
||||
import { useStore } from './useStore';
|
||||
import type { ReactFlowState } from '../types';
|
||||
|
||||
export type UseNodesInitializedOptions = {
|
||||
/** @default false */
|
||||
includeHiddenNodes?: boolean;
|
||||
};
|
||||
|
||||
@@ -22,18 +24,44 @@ const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) =>
|
||||
return true;
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
includeHiddenNodes: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook which returns true when all nodes are initialized.
|
||||
* This hook tells you whether all the nodes in a flow have been measured and given
|
||||
*a width and height. When you add a node to the flow, this hook will return
|
||||
*`false` and then `true` again once the node has been measured.
|
||||
*
|
||||
* @public
|
||||
* @param options.includeHiddenNodes - defaults to false
|
||||
* @returns boolean indicating whether all nodes are initialized
|
||||
* @returns Whether or not the nodes have been initialized by the `<ReactFlow />` component and
|
||||
* given a width and height.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useReactFlow, useNodesInitialized } from '@xyflow/react';
|
||||
*import { useEffect, useState } from 'react';
|
||||
*
|
||||
*const options = {
|
||||
* includeHiddenNodes: false,
|
||||
*};
|
||||
*
|
||||
*export default function useLayout() {
|
||||
* const { getNodes } = useReactFlow();
|
||||
* const nodesInitialized = useNodesInitialized(options);
|
||||
* const [layoutedNodes, setLayoutedNodes] = useState(getNodes());
|
||||
*
|
||||
* useEffect(() => {
|
||||
* if (nodesInitialized) {
|
||||
* setLayoutedNodes(yourLayoutingFunction(getNodes()));
|
||||
* }
|
||||
* }, [nodesInitialized]);
|
||||
*
|
||||
* return layoutedNodes;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
|
||||
export function useNodesInitialized(
|
||||
options: UseNodesInitializedOptions = {
|
||||
includeHiddenNodes: false,
|
||||
}
|
||||
): boolean {
|
||||
const initialized = useStore(selector(options));
|
||||
|
||||
return initialized;
|
||||
|
||||
@@ -1,20 +1,53 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useStoreApi } from './useStore';
|
||||
import type { OnSelectionChangeFunc } from '../types';
|
||||
import type { OnSelectionChangeFunc, Node, Edge } from '../types';
|
||||
|
||||
export type UseOnSelectionChangeOptions = {
|
||||
onChange: OnSelectionChangeFunc;
|
||||
export type UseOnSelectionChangeOptions<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
/** The handler to register. */
|
||||
onChange: OnSelectionChangeFunc<NodeType, EdgeType>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for registering an onSelectionChange handler.
|
||||
* This hook lets you listen for changes to both node and edge selection. As the
|
||||
*name implies, the callback you provide will be called whenever the selection of
|
||||
*_either_ nodes or edges changes.
|
||||
*
|
||||
* @public
|
||||
* @param params.onChange - The handler to register
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useState } from 'react';
|
||||
*import { ReactFlow, useOnSelectionChange } from '@xyflow/react';
|
||||
*
|
||||
*function SelectionDisplay() {
|
||||
* const [selectedNodes, setSelectedNodes] = useState([]);
|
||||
* const [selectedEdges, setSelectedEdges] = useState([]);
|
||||
*
|
||||
* // the passed handler has to be memoized, otherwise the hook will not work correctly
|
||||
* const onChange = useCallback(({ nodes, edges }) => {
|
||||
* setSelectedNodes(nodes.map((node) => node.id));
|
||||
* setSelectedEdges(edges.map((edge) => edge.id));
|
||||
* }, []);
|
||||
*
|
||||
* useOnSelectionChange({
|
||||
* onChange,
|
||||
* });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>Selected nodes: {selectedNodes.join(', ')}</p>
|
||||
* <p>Selected edges: {selectedEdges.join(', ')}</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly.
|
||||
*/
|
||||
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
export function useOnSelectionChange<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
|
||||
onChange,
|
||||
}: UseOnSelectionChangeOptions<NodeType, EdgeType>) {
|
||||
const store = useStoreApi<NodeType, EdgeType>();
|
||||
|
||||
useEffect(() => {
|
||||
const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange];
|
||||
|
||||
@@ -4,18 +4,35 @@ import type { OnViewportChange } from '@xyflow/system';
|
||||
import { useStoreApi } from './useStore';
|
||||
|
||||
export type UseOnViewportChangeOptions = {
|
||||
/** Gets called when the viewport starts changing. */
|
||||
onStart?: OnViewportChange;
|
||||
/** Gets called when the viewport changes. */
|
||||
onChange?: OnViewportChange;
|
||||
/** Gets called when the viewport stops changing. */
|
||||
onEnd?: OnViewportChange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for registering an onViewportChange handler.
|
||||
* The `useOnViewportChange` hook lets you listen for changes to the viewport such
|
||||
* as panning and zooming. You can provide a callback for each phase of a viewport
|
||||
* change: `onStart`, `onChange`, and `onEnd`.
|
||||
*
|
||||
* @public
|
||||
* @param params.onStart - gets called when the viewport starts changing
|
||||
* @param params.onChange - gets called when the viewport changes
|
||||
* @param params.onEnd - gets called when the viewport stops changing
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback } from 'react';
|
||||
*import { useOnViewportChange } from '@xyflow/react';
|
||||
*
|
||||
*function ViewportChangeLogger() {
|
||||
* useOnViewportChange({
|
||||
* onStart: (viewport: Viewport) => console.log('start', viewport),
|
||||
* onChange: (viewport: Viewport) => console.log('change', viewport),
|
||||
* onEnd: (viewport: Viewport) => console.log('end', viewport),
|
||||
* });
|
||||
*
|
||||
* return null;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -15,15 +15,44 @@ import useViewportHelper from './useViewportHelper';
|
||||
import { useStore, useStoreApi } from './useStore';
|
||||
import { useBatchContext } from '../components/BatchProvider';
|
||||
import { elementToRemoveChange, isEdge, isNode } from '../utils';
|
||||
import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, GeneralHelpers } from '../types';
|
||||
import type {
|
||||
ReactFlowInstance,
|
||||
Node,
|
||||
Edge,
|
||||
InternalNode,
|
||||
ReactFlowState,
|
||||
GeneralHelpers,
|
||||
FitViewOptions,
|
||||
} from '../types';
|
||||
|
||||
const selector = (s: ReactFlowState) => !!s.panZoom;
|
||||
|
||||
/**
|
||||
* Hook for accessing the ReactFlow instance.
|
||||
* This hook returns a ReactFlowInstance that can be used to update nodes and edges, manipulate the viewport, or query the current state of the flow.
|
||||
*
|
||||
* @public
|
||||
* @returns ReactFlowInstance
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback, useState } from 'react';
|
||||
*import { useReactFlow } from '@xyflow/react';
|
||||
*
|
||||
*export function NodeCounter() {
|
||||
* const reactFlow = useReactFlow();
|
||||
* const [count, setCount] = useState(0);
|
||||
* const countNodes = useCallback(() => {
|
||||
* setCount(reactFlow.getNodes().length);
|
||||
* // you need to pass it as a dependency if you are using it with useEffect or useCallback
|
||||
* // because at the first render, it's not initialized yet and some functions might not work.
|
||||
* }, [reactFlow]);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <button onClick={countNodes}>Update count</button>
|
||||
* <p>There are {count} nodes in the flow.</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): ReactFlowInstance<
|
||||
NodeType,
|
||||
@@ -72,7 +101,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
setNodes((prevNodes) =>
|
||||
prevNodes.map((node) => {
|
||||
if (node.id === id) {
|
||||
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as NodeType) : nodeUpdate;
|
||||
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node) : nodeUpdate;
|
||||
return options.replace && isNode(nextNode) ? (nextNode as NodeType) : { ...node, ...nextNode };
|
||||
}
|
||||
|
||||
@@ -89,7 +118,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
setEdges((prevEdges) =>
|
||||
prevEdges.map((edge) => {
|
||||
if (edge.id === id) {
|
||||
const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge as EdgeType) : edgeUpdate;
|
||||
const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge) : edgeUpdate;
|
||||
return options.replace && isEdge(nextEdge) ? (nextEdge as EdgeType) : { ...edge, ...nextEdge };
|
||||
}
|
||||
|
||||
@@ -184,7 +213,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
return (nodes || store.getState().nodes).filter((n) => {
|
||||
const internalNode = store.getState().nodeLookup.get(n.id);
|
||||
|
||||
if (internalNode && !isRect && (n.id === nodeOrRect!.id || !internalNode.internals.positionAbsolute)) {
|
||||
if (internalNode && !isRect && (n.id === nodeOrRect.id || !internalNode.internals.positionAbsolute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -248,6 +277,17 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
.connectionLookup.get(`${nodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`)
|
||||
?.values() ?? []
|
||||
),
|
||||
fitView: async (options: FitViewOptions<NodeType> | undefined) => {
|
||||
// We either create a new Promise or reuse the existing one
|
||||
// Even if fitView is called multiple times in a row, we only end up with a single Promise
|
||||
const fitViewResolver = store.getState().fitViewResolver ?? Promise.withResolvers<boolean>();
|
||||
|
||||
// We schedule a fitView by setting fitViewQueued and triggering a setNodes
|
||||
store.setState({ fitViewQueued: true, fitViewOptions: options, fitViewResolver });
|
||||
batchContext.nodeQueue.push((nodes) => [...nodes]);
|
||||
|
||||
return fitViewResolver.promise;
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null
|
||||
if (!domNode.current) {
|
||||
return false;
|
||||
}
|
||||
const size = getDimensions(domNode.current!);
|
||||
const size = getDimensions(domNode.current);
|
||||
|
||||
if (size.height === 0 || size.width === 0) {
|
||||
store.getState().onError?.('004', errorMessages['error004']());
|
||||
|
||||
@@ -9,16 +9,27 @@ import type { Edge, Node, ReactFlowState } from '../types';
|
||||
const zustandErrorMessage = errorMessages['error001']();
|
||||
|
||||
/**
|
||||
* Hook for accessing the internal store. Should only be used in rare cases.
|
||||
* This hook can be used to subscribe to internal state changes of the React Flow
|
||||
* component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand)
|
||||
* state management library, so you should check out their docs for more details.
|
||||
*
|
||||
* @public
|
||||
* @param selector
|
||||
* @param equalityFn
|
||||
* @returns The selected state slice
|
||||
* @param selector - A selector function that returns a slice of the flow's internal state.
|
||||
* Extracting or transforming just the state you need is a good practice to avoid unnecessary
|
||||
* re-renders.
|
||||
* @param equalityFn - A function to compare the previous and next value. This is incredibly useful
|
||||
* for preventing unnecessary re-renders. Good sensible defaults are using `Object.is` or importing
|
||||
* `zustand/shallow`, but you can be as granular as you like.
|
||||
* @returns The selected state slice.
|
||||
*
|
||||
* @example
|
||||
* const nodes = useStore((state: ReactFlowState<MyNodeType>) => state.nodes);
|
||||
* ```ts
|
||||
* const nodes = useStore((state) => state.nodes);
|
||||
* ```
|
||||
*
|
||||
* @remarks This hook should only be used if there is no other way to access the internal
|
||||
* state. For many of the common use cases, there are dedicated hooks available
|
||||
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
||||
*/
|
||||
function useStore<StateSlice = unknown>(
|
||||
selector: (state: ReactFlowState) => StateSlice,
|
||||
@@ -33,6 +44,19 @@ function useStore<StateSlice = unknown>(
|
||||
return useZustandStore(store, selector, equalityFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases, you might need to access the store directly. This hook returns the store object which can be used on demand to access the state or dispatch actions.
|
||||
*
|
||||
* @returns The store object.
|
||||
* @example
|
||||
* ```ts
|
||||
* const store = useStoreApi();
|
||||
* ```
|
||||
*
|
||||
* @remarks This hook should only be used if there is no other way to access the internal
|
||||
* state. For many of the common use cases, there are dedicated hooks available
|
||||
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
||||
*/
|
||||
function useStoreApi<NodeType extends Node = Node, EdgeType extends Edge = Edge>() {
|
||||
const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn<
|
||||
StoreApi<ReactFlowState<NodeType, EdgeType>>
|
||||
|
||||
@@ -4,10 +4,49 @@ import type { UpdateNodeInternals, InternalNodeUpdate } from '@xyflow/system';
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
|
||||
/**
|
||||
* Hook for updating node internals.
|
||||
* When you programmatically add or remove handles to a node or update a node's
|
||||
* handle position, you need to let React Flow know about it using this hook. This
|
||||
* will update the internal dimensions of the node and properly reposition handles
|
||||
* on the canvas if necessary.
|
||||
*
|
||||
* @public
|
||||
* @returns function for updating node internals
|
||||
* @returns Use this function to tell React Flow to update the internal state of one or more nodes
|
||||
* that you have changed programmatically.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback, useState } from 'react';
|
||||
*import { Handle, useUpdateNodeInternals } from '@xyflow/react';
|
||||
*
|
||||
*export default function RandomHandleNode({ id }) {
|
||||
* const updateNodeInternals = useUpdateNodeInternals();
|
||||
* const [handleCount, setHandleCount] = useState(0);
|
||||
* const randomizeHandleCount = useCallback(() => {
|
||||
* setHandleCount(Math.floor(Math.random() * 10));
|
||||
* updateNodeInternals(id);
|
||||
* }, [id, updateNodeInternals]);
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* {Array.from({ length: handleCount }).map((_, index) => (
|
||||
* <Handle
|
||||
* key={index}
|
||||
* type="target"
|
||||
* position="left"
|
||||
* id={`handle-${index}`}
|
||||
* />
|
||||
* ))}
|
||||
*
|
||||
* <div>
|
||||
* <button onClick={randomizeHandleCount}>Randomize handle count</button>
|
||||
* <p>There are {handleCount} handles on this node.</p>
|
||||
* </div>
|
||||
* </>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
* @remarks This hook can only be used in a component that is a child of a
|
||||
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
||||
*/
|
||||
export function useUpdateNodeInternals(): UpdateNodeInternals {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -11,10 +11,33 @@ const viewportSelector = (state: ReactFlowState) => ({
|
||||
});
|
||||
|
||||
/**
|
||||
* Hook for getting the current viewport from the store.
|
||||
* The `useViewport` hook is a convenient way to read the current state of the
|
||||
* {@link Viewport} in a component. Components that use this hook
|
||||
* will re-render **whenever the viewport changes**.
|
||||
*
|
||||
* @public
|
||||
* @returns The current viewport
|
||||
* @returns The current viewport.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
*```jsx
|
||||
*import { useViewport } from '@xyflow/react';
|
||||
*
|
||||
*export default function ViewportDisplay() {
|
||||
* const { x, y, zoom } = useViewport();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>
|
||||
* The viewport is currently at ({x}, {y}) and zoomed to {zoom}.
|
||||
* </p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook can only be used in a component that is a child of a
|
||||
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
||||
*/
|
||||
export function useViewport(): Viewport {
|
||||
const viewport = useStore(viewportSelector, shallow);
|
||||
|
||||
@@ -2,11 +2,9 @@ import { useMemo } from 'react';
|
||||
import {
|
||||
pointToRendererPoint,
|
||||
getViewportForBounds,
|
||||
getFitViewNodes,
|
||||
fitView,
|
||||
type XYPosition,
|
||||
rendererPointToPoint,
|
||||
getDimensions,
|
||||
SnapGrid,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
@@ -64,28 +62,6 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
||||
const [x, y, zoom] = store.getState().transform;
|
||||
return { x, y, zoom };
|
||||
},
|
||||
fitView: (options) => {
|
||||
const { nodeLookup, minZoom, maxZoom, panZoom, domNode } = store.getState();
|
||||
|
||||
if (!panZoom || !domNode) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const fitViewNodes = getFitViewNodes(nodeLookup, options);
|
||||
const { width, height } = getDimensions(domNode);
|
||||
|
||||
return fitView(
|
||||
{
|
||||
nodes: fitViewNodes,
|
||||
width,
|
||||
height,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
panZoom,
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
setCenter: async (x, y, options) => {
|
||||
const { width, height, maxZoom, panZoom } = store.getState();
|
||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
||||
@@ -119,21 +95,25 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
||||
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
screenToFlowPosition: (clientPosition: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
|
||||
const { transform, snapGrid, domNode } = store.getState();
|
||||
screenToFlowPosition: (
|
||||
clientPosition: XYPosition,
|
||||
options: { snapToGrid?: boolean; snapGrid?: SnapGrid } = {}
|
||||
) => {
|
||||
const { transform, snapGrid, snapToGrid, domNode } = store.getState();
|
||||
|
||||
if (!domNode) {
|
||||
return clientPosition;
|
||||
}
|
||||
|
||||
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
||||
|
||||
const correctedPosition = {
|
||||
x: clientPosition.x - domX,
|
||||
y: clientPosition.y - domY,
|
||||
};
|
||||
const _snapGrid = options.snapGrid ?? snapGrid;
|
||||
const _snapToGrid = options.snapToGrid ?? snapToGrid;
|
||||
|
||||
return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid);
|
||||
return pointToRendererPoint(correctedPosition, transform, _snapToGrid, _snapGrid);
|
||||
},
|
||||
flowToScreenPosition: (flowPosition: XYPosition) => {
|
||||
const { transform, domNode } = store.getState();
|
||||
|
||||
@@ -8,8 +8,8 @@ import type { Node, ReactFlowState } from '../types';
|
||||
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
|
||||
return onlyRenderVisible
|
||||
? getNodesInside<Node>(s.nodeLookup, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map(
|
||||
(node) => node.id
|
||||
)
|
||||
(node) => node.id
|
||||
)
|
||||
: Array.from(s.nodeLookup.keys());
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export { SmoothStepEdge } from './components/Edges/SmoothStepEdge';
|
||||
export { BaseEdge } from './components/Edges/BaseEdge';
|
||||
export { ReactFlowProvider } from './components/ReactFlowProvider';
|
||||
export { Panel, type PanelProps } from './components/Panel';
|
||||
export { EdgeLabelRenderer } from './components/EdgeLabelRenderer';
|
||||
export { EdgeLabelRenderer, type EdgeLabelRendererProps } from './components/EdgeLabelRenderer';
|
||||
export { ViewportPortal } from './components/ViewportPortal';
|
||||
|
||||
export { useReactFlow } from './hooks/useReactFlow';
|
||||
@@ -106,6 +106,7 @@ export {
|
||||
type FinalConnectionState,
|
||||
type ConnectionInProgress,
|
||||
type NoConnection,
|
||||
type NodeConnection,
|
||||
} from '@xyflow/system';
|
||||
|
||||
// we need this workaround to prevent a duplicate identifier error
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { createWithEqualityFn } from 'zustand/traditional';
|
||||
import {
|
||||
getFitViewNodes,
|
||||
fitView as fitViewSystem,
|
||||
adoptUserNodes,
|
||||
updateAbsolutePositions,
|
||||
panBy as panBySystem,
|
||||
@@ -15,11 +13,12 @@ import {
|
||||
initialConnection,
|
||||
NodeOrigin,
|
||||
CoordinateExtent,
|
||||
fitViewport,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||
import getInitialState from './initialState';
|
||||
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions } from '../types';
|
||||
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams } from '../types';
|
||||
|
||||
const createStore = ({
|
||||
nodes,
|
||||
@@ -42,25 +41,60 @@ const createStore = ({
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
}) =>
|
||||
createWithEqualityFn<ReactFlowState>(
|
||||
(set, get) => ({
|
||||
createWithEqualityFn<ReactFlowState>((set, get) => {
|
||||
async function resolveFitView() {
|
||||
const { nodeLookup, panZoom, fitViewOptions, fitViewResolver, width, height, minZoom, maxZoom } = get();
|
||||
|
||||
if (!panZoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
await fitViewport(
|
||||
{
|
||||
nodes: nodeLookup,
|
||||
width,
|
||||
height,
|
||||
panZoom,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
},
|
||||
fitViewOptions
|
||||
);
|
||||
|
||||
fitViewResolver?.resolve(true);
|
||||
/**
|
||||
* wait for the fitViewport to resolve before deleting the resolver,
|
||||
* we want to reuse the old resolver if the user calls fitView again in the mean time
|
||||
*/
|
||||
set({ fitViewResolver: null });
|
||||
}
|
||||
|
||||
return {
|
||||
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
||||
setNodes: (nodes: Node[]) => {
|
||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
// setNodes() is called exclusively in response to user actions:
|
||||
// - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||
// - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
||||
//
|
||||
// When this happens, we take the note objects passed by the user and extend them with fields
|
||||
// relevant for internal React Flow operations.
|
||||
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get();
|
||||
/*
|
||||
* setNodes() is called exclusively in response to user actions:
|
||||
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||
* - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
||||
*
|
||||
* When this happens, we take the note objects passed by the user and extend them with fields
|
||||
* relevant for internal React Flow operations.
|
||||
*/
|
||||
|
||||
const nodesInitialized = adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
elevateNodesOnSelect,
|
||||
checkEquality: true,
|
||||
});
|
||||
|
||||
set({ nodes });
|
||||
if (fitViewQueued && nodesInitialized) {
|
||||
resolveFitView();
|
||||
set({ nodes, fitViewQueued: false, fitViewOptions: undefined });
|
||||
} else {
|
||||
set({ nodes });
|
||||
}
|
||||
},
|
||||
setEdges: (edges: Edge[]) => {
|
||||
const { connectionLookup, edgeLookup } = get();
|
||||
@@ -81,23 +115,14 @@ const createStore = ({
|
||||
set({ hasDefaultEdges: true });
|
||||
}
|
||||
},
|
||||
// Every node gets registerd at a ResizeObserver. Whenever a node
|
||||
// changes its dimensions, this function is called to measure the
|
||||
// new dimensions and update the nodes.
|
||||
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
||||
const {
|
||||
triggerNodeChanges,
|
||||
nodeLookup,
|
||||
parentLookup,
|
||||
fitViewOnInit,
|
||||
fitViewDone,
|
||||
fitViewOnInitOptions,
|
||||
domNode,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
debug,
|
||||
fitViewSync,
|
||||
} = get();
|
||||
/*
|
||||
* Every node gets registerd at a ResizeObserver. Whenever a node
|
||||
* changes its dimensions, this function is called to measure the
|
||||
* new dimensions and update the nodes.
|
||||
*/
|
||||
updateNodeInternals: (updates) => {
|
||||
const { triggerNodeChanges, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent, debug, fitViewQueued } =
|
||||
get();
|
||||
|
||||
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
||||
updates,
|
||||
@@ -114,23 +139,9 @@ const createStore = ({
|
||||
|
||||
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
|
||||
|
||||
if (params.triggerFitView) {
|
||||
// we call fitView once initially after all dimensions are set
|
||||
let nextFitViewDone = fitViewDone;
|
||||
|
||||
if (!fitViewDone && fitViewOnInit) {
|
||||
nextFitViewDone = fitViewSync({
|
||||
...fitViewOnInitOptions,
|
||||
nodes: fitViewOnInitOptions?.nodes,
|
||||
});
|
||||
}
|
||||
|
||||
// here we are cirmumventing the onNodesChange handler
|
||||
// in order to be able to display nodes even if the user
|
||||
// has not provided an onNodesChange handler.
|
||||
// Nodes are only rendered if they have a width and height
|
||||
// attribute which they get from this handler.
|
||||
set({ fitViewDone: nextFitViewDone });
|
||||
if (fitViewQueued) {
|
||||
resolveFitView();
|
||||
set({ fitViewQueued: false, fitViewOptions: undefined });
|
||||
} else {
|
||||
// we always want to trigger useStore calls whenever updateNodeInternals is called
|
||||
set({});
|
||||
@@ -146,9 +157,12 @@ const createStore = ({
|
||||
updateNodePositions: (nodeDragItems, dragging = false) => {
|
||||
const parentExpandChildren: ParentExpandChild[] = [];
|
||||
const changes = [];
|
||||
const { nodeLookup, triggerNodeChanges } = get();
|
||||
|
||||
for (const [id, dragItem] of nodeDragItems) {
|
||||
const expandParent = !!(dragItem?.expandParent && dragItem?.parentId && dragItem?.position);
|
||||
// we are using the nodelookup to be sure to use the current expandParent and parentId value
|
||||
const node = nodeLookup.get(id);
|
||||
const expandParent = !!(node?.expandParent && node?.parentId && dragItem?.position);
|
||||
|
||||
const change: NodeChange = {
|
||||
id,
|
||||
@@ -162,14 +176,14 @@ const createStore = ({
|
||||
dragging,
|
||||
};
|
||||
|
||||
if (expandParent) {
|
||||
if (expandParent && node.parentId) {
|
||||
parentExpandChildren.push({
|
||||
id,
|
||||
parentId: dragItem.parentId!,
|
||||
parentId: node.parentId,
|
||||
rect: {
|
||||
...dragItem.internals.positionAbsolute,
|
||||
width: dragItem.measured.width!,
|
||||
height: dragItem.measured.height!,
|
||||
width: dragItem.measured.width ?? 0,
|
||||
height: dragItem.measured.height ?? 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -178,12 +192,12 @@ const createStore = ({
|
||||
}
|
||||
|
||||
if (parentExpandChildren.length > 0) {
|
||||
const { nodeLookup, parentLookup, nodeOrigin } = get();
|
||||
const { parentLookup, nodeOrigin } = get();
|
||||
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
|
||||
changes.push(...parentExpandChanges);
|
||||
}
|
||||
|
||||
get().triggerNodeChanges(changes);
|
||||
triggerNodeChanges(changes);
|
||||
},
|
||||
triggerNodeChanges: (changes) => {
|
||||
const { onNodesChange, setNodes, nodes, hasDefaultNodes, debug } = get();
|
||||
@@ -222,7 +236,7 @@ const createStore = ({
|
||||
|
||||
if (multiSelectionActive) {
|
||||
const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
||||
triggerNodeChanges(nodeChanges as NodeSelectionChange[]);
|
||||
triggerNodeChanges(nodeChanges);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -234,7 +248,7 @@ const createStore = ({
|
||||
|
||||
if (multiSelectionActive) {
|
||||
const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
||||
triggerEdgeChanges(changedEdges as EdgeSelectionChange[]);
|
||||
triggerEdgeChanges(changedEdges);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,8 +262,10 @@ const createStore = ({
|
||||
const nodeChanges = nodesToUnselect.map((n) => {
|
||||
const internalNode = nodeLookup.get(n.id);
|
||||
if (internalNode) {
|
||||
// we need to unselect the internal node that was selected previously before we
|
||||
// send the change to the user to prevent it to be selected while dragging the new node
|
||||
/*
|
||||
* we need to unselect the internal node that was selected previously before we
|
||||
* send the change to the user to prevent it to be selected while dragging the new node
|
||||
*/
|
||||
internalNode.selected = false;
|
||||
}
|
||||
|
||||
@@ -257,8 +273,8 @@ const createStore = ({
|
||||
});
|
||||
const edgeChanges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
|
||||
|
||||
triggerNodeChanges(nodeChanges as NodeSelectionChange[]);
|
||||
triggerEdgeChanges(edgeChanges as EdgeSelectionChange[]);
|
||||
triggerNodeChanges(nodeChanges);
|
||||
triggerEdgeChanges(edgeChanges);
|
||||
},
|
||||
setMinZoom: (minZoom) => {
|
||||
const { panZoom, maxZoom } = get();
|
||||
@@ -284,11 +300,11 @@ const createStore = ({
|
||||
const { edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
|
||||
|
||||
const nodeChanges = nodes.reduce<NodeSelectionChange[]>(
|
||||
(res, node) => (node.selected ? [...res, createSelectionChange(node.id, false) as NodeSelectionChange] : res),
|
||||
(res, node) => (node.selected ? [...res, createSelectionChange(node.id, false)] : res),
|
||||
[]
|
||||
);
|
||||
const edgeChanges = edges.reduce<EdgeSelectionChange[]>(
|
||||
(res, edge) => (edge.selected ? [...res, createSelectionChange(edge.id, false) as EdgeSelectionChange] : res),
|
||||
(res, edge) => (edge.selected ? [...res, createSelectionChange(edge.id, false)] : res),
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -321,52 +337,6 @@ const createStore = ({
|
||||
|
||||
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
|
||||
},
|
||||
fitView: (options?: FitViewOptions): Promise<boolean> => {
|
||||
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
||||
|
||||
if (!panZoom) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const fitViewNodes = getFitViewNodes(nodeLookup, options);
|
||||
|
||||
return fitViewSystem(
|
||||
{
|
||||
nodes: fitViewNodes,
|
||||
width,
|
||||
height,
|
||||
panZoom,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
// we can't call an asnychronous function in updateNodeInternals
|
||||
// for that we created this sync version of fitView
|
||||
fitViewSync: (options?: FitViewOptions): boolean => {
|
||||
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
||||
|
||||
if (!panZoom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fitViewNodes = getFitViewNodes(nodeLookup, options);
|
||||
|
||||
fitViewSystem(
|
||||
{
|
||||
nodes: fitViewNodes,
|
||||
width,
|
||||
height,
|
||||
panZoom,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
return fitViewNodes.size > 0;
|
||||
},
|
||||
cancelConnection: () => {
|
||||
set({
|
||||
connection: { ...initialConnection },
|
||||
@@ -377,8 +347,7 @@ const createStore = ({
|
||||
},
|
||||
|
||||
reset: () => set({ ...getInitialState() }),
|
||||
}),
|
||||
Object.is
|
||||
);
|
||||
};
|
||||
}, Object.is);
|
||||
|
||||
export { createStore };
|
||||
|
||||
@@ -104,13 +104,14 @@ const getInitialState = ({
|
||||
elementsSelectable: true,
|
||||
elevateNodesOnSelect: true,
|
||||
elevateEdgesOnSelect: false,
|
||||
fitViewOnInit: false,
|
||||
fitViewDone: false,
|
||||
fitViewOnInitOptions: undefined,
|
||||
selectNodesOnDrag: true,
|
||||
|
||||
multiSelectionActive: false,
|
||||
|
||||
fitViewQueued: fitView ?? false,
|
||||
fitViewOptions: undefined,
|
||||
fitViewResolver: null,
|
||||
|
||||
connection: { ...initialConnection },
|
||||
connectionClickStartHandle: null,
|
||||
connectOnClick: true,
|
||||
|
||||
@@ -52,7 +52,9 @@ import type {
|
||||
*/
|
||||
export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends Edge = Edge>
|
||||
extends Omit<HTMLAttributes<HTMLDivElement>, 'onError'> {
|
||||
/** An array of nodes to render in a controlled flow.
|
||||
/**
|
||||
* An array of nodes to render in a controlled flow.
|
||||
* @default []
|
||||
* @example
|
||||
* const nodes = [
|
||||
* {
|
||||
@@ -64,7 +66,9 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* ];
|
||||
*/
|
||||
nodes?: NodeType[];
|
||||
/** An array of edges to render in a controlled flow.
|
||||
/**
|
||||
* An array of edges to render in a controlled flow.
|
||||
* @default []
|
||||
* @example
|
||||
* const edges = [
|
||||
* {
|
||||
@@ -79,7 +83,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
defaultNodes?: NodeType[];
|
||||
/** The initial edges to render in an uncontrolled flow. */
|
||||
defaultEdges?: EdgeType[];
|
||||
/** Defaults to be applied to all new edges that are added to the flow.
|
||||
/**
|
||||
* Defaults to be applied to all new edges that are added to the flow.
|
||||
*
|
||||
* Properties on a new edge will override these defaults if they exist.
|
||||
* @example
|
||||
@@ -99,57 +104,75 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* }
|
||||
*/
|
||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||
/** This event handler is called when a user clicks on a node */
|
||||
/** This event handler is called when a user clicks on a node. */
|
||||
onNodeClick?: NodeMouseHandler<NodeType>;
|
||||
/** This event handler is called when a user double clicks on a node */
|
||||
/** This event handler is called when a user double-clicks on a node. */
|
||||
onNodeDoubleClick?: NodeMouseHandler<NodeType>;
|
||||
/** This event handler is called when mouse of a user enters a node */
|
||||
/** This event handler is called when mouse of a user enters a node. */
|
||||
onNodeMouseEnter?: NodeMouseHandler<NodeType>;
|
||||
/** This event handler is called when mouse of a user moves over a node */
|
||||
/** This event handler is called when mouse of a user moves over a node. */
|
||||
onNodeMouseMove?: NodeMouseHandler<NodeType>;
|
||||
/** This event handler is called when mouse of a user leaves a node */
|
||||
/** This event handler is called when mouse of a user leaves a node. */
|
||||
onNodeMouseLeave?: NodeMouseHandler<NodeType>;
|
||||
/** This event handler is called when a user right clicks on a node */
|
||||
/** This event handler is called when a user right-clicks on a node. */
|
||||
onNodeContextMenu?: NodeMouseHandler<NodeType>;
|
||||
/** This event handler is called when a user starts to drag a node */
|
||||
/** This event handler is called when a user starts to drag a node. */
|
||||
onNodeDragStart?: OnNodeDrag<NodeType>;
|
||||
/** This event handler is called when a user drags a node */
|
||||
/** This event handler is called when a user drags a node. */
|
||||
onNodeDrag?: OnNodeDrag<NodeType>;
|
||||
/** This event handler is called when a user stops dragging a node */
|
||||
/** This event handler is called when a user stops dragging a node. */
|
||||
onNodeDragStop?: OnNodeDrag<NodeType>;
|
||||
/** This event handler is called when a user clicks on an edge */
|
||||
/** This event handler is called when a user clicks on an edge. */
|
||||
onEdgeClick?: (event: ReactMouseEvent, edge: EdgeType) => void;
|
||||
/** This event handler is called when a user right clicks on an edge */
|
||||
/** This event handler is called when a user right-clicks on an edge. */
|
||||
onEdgeContextMenu?: EdgeMouseHandler<EdgeType>;
|
||||
/** This event handler is called when mouse of a user enters an edge */
|
||||
/** This event handler is called when mouse of a user enters an edge. */
|
||||
onEdgeMouseEnter?: EdgeMouseHandler<EdgeType>;
|
||||
/** This event handler is called when mouse of a user moves over an edge */
|
||||
/** This event handler is called when mouse of a user moves over an edge. */
|
||||
onEdgeMouseMove?: EdgeMouseHandler<EdgeType>;
|
||||
/** This event handler is called when mouse of a user leaves an edge */
|
||||
/** This event handler is called when mouse of a user leaves an edge. */
|
||||
onEdgeMouseLeave?: EdgeMouseHandler<EdgeType>;
|
||||
/** This event handler is called when a user double clicks on an edge */
|
||||
/** This event handler is called when a user double-clicks on an edge. */
|
||||
onEdgeDoubleClick?: EdgeMouseHandler<EdgeType>;
|
||||
/**
|
||||
* This handler is called when the source or target of a reconnectable edge is dragged from the
|
||||
* current node. It will fire even if the edge's source or target do not end up changing.
|
||||
*
|
||||
* You can use the `reconnectEdge` utility to convert the connection to a new edge.
|
||||
*/
|
||||
onReconnect?: OnReconnect<EdgeType>;
|
||||
/**
|
||||
* This event fires when the user begins dragging the source or target of an editable edge.
|
||||
*/
|
||||
onReconnectStart?: (event: ReactMouseEvent, edge: EdgeType, handleType: HandleType) => void;
|
||||
/**
|
||||
* This event fires when the user releases the source or target of an editable edge. It is called
|
||||
* even if an edge update does not occur.
|
||||
*
|
||||
*/
|
||||
onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void;
|
||||
/** This event handler is called when a Node is updated
|
||||
/**
|
||||
* Use this event handler to add interactivity to a controlled flow.
|
||||
* It is called on node drag, select, and move.
|
||||
* @example // Use NodesState hook to create edges and get onNodesChange handler
|
||||
* import ReactFlow, { useNodesState } from '@xyflow/react';
|
||||
* const [edges, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
*
|
||||
* return (<ReactFlow onNodeChange={onNodeChange} {...rest} />)
|
||||
* @example // Use helper function to update edge
|
||||
* @example // Use helper function to update node
|
||||
* import ReactFlow, { applyNodeChanges } from '@xyflow/react';
|
||||
*
|
||||
* const onNodeChange = useCallback(
|
||||
* (changes) => setNode((eds) => applyNodeChanges(changes, eds)),
|
||||
* (changes) => setNode((nds) => applyNodeChanges(changes, nds)),
|
||||
* [],
|
||||
* );
|
||||
*
|
||||
* return (<ReactFlow onNodeChange={onNodeChange} {...rest} />)
|
||||
*/
|
||||
onNodesChange?: OnNodesChange<NodeType>;
|
||||
/** This event handler is called when a Edge is updated
|
||||
/**
|
||||
* Use this event handler to add interactivity to a controlled flow. It is called on edge select
|
||||
* and remove.
|
||||
* @example // Use EdgesState hook to create edges and get onEdgesChange handler
|
||||
* import ReactFlow, { useEdgesState } from '@xyflow/react';
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
@@ -166,24 +189,28 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* return (<ReactFlow onEdgesChange={onEdgesChange} {...rest} />)
|
||||
*/
|
||||
onEdgesChange?: OnEdgesChange<EdgeType>;
|
||||
/** This event handler gets called when a Node is deleted */
|
||||
/** This event handler gets called when a node is deleted. */
|
||||
onNodesDelete?: OnNodesDelete<NodeType>;
|
||||
/** This event handler gets called when a Edge is deleted */
|
||||
/** This event handler gets called when an edge is deleted. */
|
||||
onEdgesDelete?: OnEdgesDelete<EdgeType>;
|
||||
/** This event handler gets called when a Node or Edge is deleted */
|
||||
/** This event handler gets called when a node or edge is deleted. */
|
||||
onDelete?: OnDelete<NodeType, EdgeType>;
|
||||
/** This event handler gets called when a user starts to drag a selection box */
|
||||
/** This event handler gets called when a user starts to drag a selection box. */
|
||||
onSelectionDragStart?: SelectionDragHandler<NodeType>;
|
||||
/** This event handler gets called when a user drags a selection box */
|
||||
/** This event handler gets called when a user drags a selection box. */
|
||||
onSelectionDrag?: SelectionDragHandler<NodeType>;
|
||||
/** This event handler gets called when a user stops dragging a selection box */
|
||||
/** This event handler gets called when a user stops dragging a selection box. */
|
||||
onSelectionDragStop?: SelectionDragHandler<NodeType>;
|
||||
onSelectionStart?: (event: ReactMouseEvent) => void;
|
||||
onSelectionEnd?: (event: ReactMouseEvent) => void;
|
||||
/**
|
||||
* This event handler is called when a user right-clicks on a node selection.
|
||||
*/
|
||||
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void;
|
||||
/** When a connection line is completed and two nodes are connected by the user, this event fires with the new connection.
|
||||
/**
|
||||
* When a connection line is completed and two nodes are connected by the user, this event fires with the new connection.
|
||||
*
|
||||
* You can use the addEdge utility to convert the connection to a complete edge.
|
||||
* You can use the `addEdge` utility to convert the connection to a complete edge.
|
||||
* @example // Use helper function to update edges onConnect
|
||||
* import ReactFlow, { addEdge } from '@xyflow/react';
|
||||
*
|
||||
@@ -195,178 +222,260 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* return (<ReactFlow onConnect={onConnect} {...rest} />)
|
||||
*/
|
||||
onConnect?: OnConnect;
|
||||
/** This event handler gets called when a user starts to drag a connection line */
|
||||
/** This event handler gets called when a user starts to drag a connection line. */
|
||||
onConnectStart?: OnConnectStart;
|
||||
/** This event handler gets called when a user stops dragging a connection line */
|
||||
/**
|
||||
* This callback will fire regardless of whether a valid connection could be made or not. You can
|
||||
* use the second `connectionState` parameter to have different behavior when a connection was
|
||||
* unsuccessful.
|
||||
*/
|
||||
onConnectEnd?: OnConnectEnd;
|
||||
onClickConnectStart?: OnConnectStart;
|
||||
onClickConnectEnd?: OnConnectEnd;
|
||||
/** This event handler gets called when a flow has finished initializing */
|
||||
/**
|
||||
* The `onInit` callback is called when the viewport is initialized. At this point you can use the
|
||||
* instance to call methods like `fitView` or `zoomTo`.
|
||||
*/
|
||||
onInit?: OnInit<NodeType, EdgeType>;
|
||||
/** This event handler is called while the user is either panning or zooming the viewport. */
|
||||
onMove?: OnMove;
|
||||
/** This event handler gets called when a user starts to pan or zoom the viewport */
|
||||
/** This event handler is called when the user begins to pan or zoom the viewport. */
|
||||
onMoveStart?: OnMoveStart;
|
||||
/** This event handler gets called when a user stops panning or zooming the viewport */
|
||||
/**
|
||||
* This event handler is called when panning or zooming viewport movement stops.
|
||||
* If the movement is not user-initiated, the event parameter will be `null`.
|
||||
*/
|
||||
onMoveEnd?: OnMoveEnd;
|
||||
/** This event handler gets called when a user changes group of selected elements in the flow */
|
||||
onSelectionChange?: OnSelectionChangeFunc;
|
||||
/** This event handler gets called when user scroll inside the pane */
|
||||
/** This event handler gets called when a user changes group of selected elements in the flow. */
|
||||
onSelectionChange?: OnSelectionChangeFunc<NodeType, EdgeType>;
|
||||
/** This event handler gets called when user scroll inside the pane. */
|
||||
onPaneScroll?: (event?: WheelEvent) => void;
|
||||
/** This event handler gets called when user clicks inside the pane */
|
||||
/** This event handler gets called when user clicks inside the pane. */
|
||||
onPaneClick?: (event: ReactMouseEvent) => void;
|
||||
/** This event handler gets called when user right clicks inside the pane */
|
||||
/** This event handler gets called when user right clicks inside the pane. */
|
||||
onPaneContextMenu?: (event: ReactMouseEvent | MouseEvent) => void;
|
||||
/** This event handler gets called when mouse enters the pane */
|
||||
/** This event handler gets called when mouse enters the pane. */
|
||||
onPaneMouseEnter?: (event: ReactMouseEvent) => void;
|
||||
/** This event handler gets called when mouse moves over the pane */
|
||||
/** This event handler gets called when mouse moves over the pane. */
|
||||
onPaneMouseMove?: (event: ReactMouseEvent) => void;
|
||||
/** This event handler gets called when mouse leaves the pane */
|
||||
/** This event handler gets called when mouse leaves the pane. */
|
||||
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
|
||||
/** Distance that the mouse can move between mousedown/up that will trigger a click
|
||||
/**
|
||||
* Distance that the mouse can move between mousedown/up that will trigger a click.
|
||||
* @default 0
|
||||
*/
|
||||
paneClickDistance?: number;
|
||||
/** Distance that the mouse can move between mousedown/up that will trigger a click
|
||||
/**
|
||||
* Distance that the mouse can move between mousedown/up that will trigger a click.
|
||||
* @default 0
|
||||
*/
|
||||
nodeClickDistance?: number;
|
||||
/** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */
|
||||
/**
|
||||
* This handler is called before nodes or edges are deleted, allowing the deletion to be aborted
|
||||
* by returning `false` or modified by returning updated nodes and edges.
|
||||
*/
|
||||
onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>;
|
||||
/** Custom node types to be available in a flow.
|
||||
/**
|
||||
* Custom node types to be available in a flow.
|
||||
*
|
||||
* React Flow matches a node's type to a component in the nodeTypes object.
|
||||
* React Flow matches a node's type to a component in the `nodeTypes` object.
|
||||
* @TODO check if @default is correct
|
||||
* @default {
|
||||
* input: InputNode,
|
||||
* default: DefaultNode,
|
||||
* output: OutputNode,
|
||||
* group: GroupNode
|
||||
* }
|
||||
* @example
|
||||
* import CustomNode from './CustomNode';
|
||||
*
|
||||
* const nodeTypes = { nameOfNodeType: CustomNode };
|
||||
*/
|
||||
nodeTypes?: NodeTypes;
|
||||
/** Custom edge types to be available in a flow.
|
||||
/**
|
||||
* Custom edge types to be available in a flow.
|
||||
*
|
||||
* React Flow matches an edge's type to a component in the edgeTypes object.
|
||||
* React Flow matches an edge's type to a component in the `edgeTypes` object.
|
||||
* @TODO check if @default is correct
|
||||
* @default {
|
||||
* default: BezierEdge,
|
||||
* straight: StraightEdge,
|
||||
* step: StepEdge,
|
||||
* smoothstep: SmoothStepEdge,
|
||||
* simplebezier: SimpleBezier
|
||||
* }
|
||||
* @example
|
||||
* import CustomEdge from './CustomEdge';
|
||||
*
|
||||
* const edgeTypes = { nameOfEdgeType: CustomEdge };
|
||||
*/
|
||||
edgeTypes?: EdgeTypes;
|
||||
/** The type of edge path to use for connection lines.
|
||||
/**
|
||||
* The type of edge path to use for connection lines.
|
||||
*
|
||||
* Although created edges can be of any type, React Flow needs to know what type of path to render for the connection line before the edge is created!
|
||||
* @default ConnectionLineType.Bezier
|
||||
*/
|
||||
connectionLineType?: ConnectionLineType;
|
||||
/** Styles to be applied to the connection line */
|
||||
/** Styles to be applied to the connection line. */
|
||||
connectionLineStyle?: CSSProperties;
|
||||
/** React Component to be used as a connection line */
|
||||
connectionLineComponent?: ConnectionLineComponent;
|
||||
/** Styles to be applied to the container of the connection line */
|
||||
/** React Component to be used as a connection line. */
|
||||
connectionLineComponent?: ConnectionLineComponent<NodeType>;
|
||||
/** Styles to be applied to the container of the connection line. */
|
||||
connectionLineContainerStyle?: CSSProperties;
|
||||
/** 'strict' connection mode will only allow you to connect source handles to target handles.
|
||||
*
|
||||
* 'loose' connection mode will allow you to connect handles of any type to one another.
|
||||
/**
|
||||
* A loose connection mode will allow you to connect handles with differing types, including
|
||||
* source-to-source connections. However, it does not support target-to-target connections. Strict
|
||||
* mode allows only connections between source handles and target handles.
|
||||
* @default 'strict'
|
||||
*/
|
||||
connectionMode?: ConnectionMode;
|
||||
/** Pressing down this key deletes all selected nodes & edges.
|
||||
/**
|
||||
* If set, pressing the key or chord will delete any selected nodes and edges. Passing an array
|
||||
* represents multiple keys that can be pressed.
|
||||
*
|
||||
* For example, `["Delete", "Backspace"]` will delete selected elements when either key is pressed.
|
||||
* @default 'Backspace'
|
||||
*/
|
||||
deleteKeyCode?: KeyCode | null;
|
||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||
/**
|
||||
* If set, holding this key will let you click and drag to draw a selection box around multiple
|
||||
* nodes and edges. Passing an array represents multiple keys that can be pressed.
|
||||
*
|
||||
* By setting this prop to null you can disable this functionality.
|
||||
* @default 'Space'
|
||||
* For example, `["Shift", "Meta"]` will allow you to draw a selection box when either key is
|
||||
* pressed.
|
||||
* @default 'Shift'
|
||||
*/
|
||||
selectionKeyCode?: KeyCode | null;
|
||||
/** Select multiple elements with a selection box, without pressing down selectionKey */
|
||||
/**
|
||||
* Select multiple elements with a selection box, without pressing down `selectionKey`.
|
||||
* @default false
|
||||
*/
|
||||
selectionOnDrag?: boolean;
|
||||
/** When set to "partial", when the user creates a selection box by click and dragging nodes that are only partially in the box are still selected.
|
||||
/**
|
||||
* When set to `"partial"`, when the user creates a selection box by click and dragging nodes that
|
||||
* are only partially in the box are still selected.
|
||||
* @default 'full'
|
||||
*/
|
||||
selectionMode?: SelectionMode;
|
||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||
/**
|
||||
* If a key is set, you can pan the viewport while that key is held down even if `panOnScroll`
|
||||
* is set to `false`.
|
||||
*
|
||||
* By setting this prop to null you can disable this functionality.
|
||||
* By setting this prop to `null` you can disable this functionality.
|
||||
* @default 'Space'
|
||||
*/
|
||||
panActivationKeyCode?: KeyCode | null;
|
||||
/** Pressing down this key you can select multiple elements by clicking.
|
||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||
/**
|
||||
* Pressing down this key you can select multiple elements by clicking.
|
||||
* @default "Meta" for macOS, "Control" for other systems
|
||||
*/
|
||||
multiSelectionKeyCode?: KeyCode | null;
|
||||
/** If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
||||
/**
|
||||
* If a key is set, you can zoom the viewport while that key is held down even if `panOnScroll`
|
||||
* is set to `false`.
|
||||
*
|
||||
* By setting this prop to null you can disable this functionality.
|
||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||
* */
|
||||
* By setting this prop to `null` you can disable this functionality.
|
||||
* @default "Meta" for macOS, "Control" for other systems
|
||||
*
|
||||
*/
|
||||
zoomActivationKeyCode?: KeyCode | null;
|
||||
/** Set this prop to make the flow snap to the grid */
|
||||
/** When enabled, nodes will snap to the grid when dragged. */
|
||||
snapToGrid?: boolean;
|
||||
/** Grid all nodes will snap to
|
||||
/**
|
||||
* If `snapToGrid` is enabled, this prop configures the grid that nodes will snap to.
|
||||
* @example [20, 20]
|
||||
*/
|
||||
snapGrid?: SnapGrid;
|
||||
/** You can enable this optimisation to instruct Svelte Flow to only render nodes and edges that would be visible in the viewport.
|
||||
/**
|
||||
* You can enable this optimisation to instruct React Flow to only render nodes and edges that would be visible in the viewport.
|
||||
*
|
||||
* This might improve performance when you have a large number of nodes and edges but also adds an overhead.
|
||||
* @default false
|
||||
*/
|
||||
onlyRenderVisibleElements?: boolean;
|
||||
/** Controls if all nodes should be draggable
|
||||
/**
|
||||
* Controls whether all nodes should be draggable or not. Individual nodes can override this
|
||||
* setting by setting their `draggable` prop. If you want to use the mouse handlers on
|
||||
* non-draggable nodes, you need to add the `"nopan"` class to those nodes.
|
||||
* @default true
|
||||
*/
|
||||
nodesDraggable?: boolean;
|
||||
/** Controls if all nodes should be connectable to each other
|
||||
/**
|
||||
* Controls whether all nodes should be connectable or not. Individual nodes can override this
|
||||
* setting by setting their `connectable` prop.
|
||||
* @default true
|
||||
*/
|
||||
nodesConnectable?: boolean;
|
||||
/** Controls if all nodes should be focusable
|
||||
/**
|
||||
* When `true`, focus between nodes can be cycled with the `Tab` key and selected with the `Enter`
|
||||
* key. This option can be overridden by individual nodes by setting their `focusable` prop.
|
||||
* @default true
|
||||
*/
|
||||
nodesFocusable?: boolean;
|
||||
/** Defines nodes relative position to its coordinates
|
||||
/**
|
||||
* The origin of the node to use when placing it in the flow or looking up its `x` and `y`
|
||||
* position. An origin of `[0, 0]` means that a node's top left corner will be placed at the `x`
|
||||
* and `y` position.
|
||||
* @default [0, 0]
|
||||
* @example
|
||||
* [0, 0] // default, top left
|
||||
* [0.5, 0.5] // center
|
||||
* [1, 1] // bottom right
|
||||
*/
|
||||
nodeOrigin?: NodeOrigin;
|
||||
/** Controls if all edges should be focusable
|
||||
/**
|
||||
* When `true`, focus between edges can be cycled with the `Tab` key and selected with the `Enter`
|
||||
* key. This option can be overridden by individual edges by setting their `focusable` prop.
|
||||
* @default true
|
||||
*/
|
||||
edgesFocusable?: boolean;
|
||||
/** Controls if all edges should be updateable
|
||||
/**
|
||||
* Whether edges can be updated once they are created. When both this prop is `true` and an
|
||||
* `onReconnect` handler is provided, the user can drag an existing edge to a new source or
|
||||
* target. Individual edges can override this value with their reconnectable property.
|
||||
* @default true
|
||||
*/
|
||||
edgesReconnectable?: boolean;
|
||||
/** Controls if all elements should (nodes & edges) be selectable
|
||||
/**
|
||||
* When `true`, elements (nodes and edges) can be selected by clicking on them. This option can be
|
||||
* overridden by individual elements by setting their `selectable` prop.
|
||||
* @default true
|
||||
*/
|
||||
elementsSelectable?: boolean;
|
||||
/** If true, nodes get selected on drag
|
||||
/**
|
||||
* If `true`, nodes get selected on drag.
|
||||
* @default true
|
||||
*/
|
||||
selectNodesOnDrag?: boolean;
|
||||
/** Enableing this prop allows users to pan the viewport by clicking and dragging.
|
||||
/**
|
||||
* Enabling this prop allows users to pan the viewport by clicking and dragging.
|
||||
*
|
||||
* You can also set this prop to an array of numbers to limit which mouse buttons can activate panning.
|
||||
* @default true
|
||||
* @example [0, 2] // allows panning with the left and right mouse buttons
|
||||
* [0, 1, 2, 3, 4] // allows panning with all mouse buttons
|
||||
*/
|
||||
panOnDrag?: boolean | number[];
|
||||
/** Minimum zoom level
|
||||
/**
|
||||
* Minimum zoom level.
|
||||
* @default 0.5
|
||||
*/
|
||||
minZoom?: number;
|
||||
/** Maximum zoom level
|
||||
/**
|
||||
* Maximum zoom level.
|
||||
* @default 2
|
||||
*/
|
||||
maxZoom?: number;
|
||||
/** Controlled viewport to be used instead of internal one */
|
||||
/**
|
||||
* When you pass a `viewport` prop, it's controlled, and you also need to pass `onViewportChange`
|
||||
* to handle internal changes.
|
||||
*/
|
||||
viewport?: Viewport;
|
||||
/** Sets the initial position and zoom of the viewport.
|
||||
*
|
||||
* If a default viewport is provided but fitView is enabled, the default viewport will be ignored.
|
||||
/**
|
||||
* Sets the initial position and zoom of the viewport. If a default viewport is provided but
|
||||
* `fitView` is enabled, the default viewport will be ignored.
|
||||
* @default { x: 0, y: 0, zoom: 1 }
|
||||
* @example
|
||||
* const initialViewport = {
|
||||
* zoom: 0.5,
|
||||
@@ -375,59 +484,103 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
*/
|
||||
defaultViewport?: Viewport;
|
||||
/**
|
||||
* Gets called when the viewport changes.
|
||||
* Used when working with a controlled viewport for updating the user viewport state.
|
||||
*/
|
||||
onViewportChange?: (viewport: Viewport) => void;
|
||||
/** By default the viewport extends infinitely. You can use this prop to set a boundary.
|
||||
/**
|
||||
* By default, the viewport extends infinitely. You can use this prop to set a boundary.
|
||||
*
|
||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||
* @default [[-∞, -∞], [+∞, +∞]]
|
||||
* @example [[-1000, -10000], [1000, 1000]]
|
||||
*/
|
||||
translateExtent?: CoordinateExtent;
|
||||
/** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
|
||||
/**
|
||||
* Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
|
||||
* @default true
|
||||
*/
|
||||
preventScrolling?: boolean;
|
||||
/** By default nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
||||
/**
|
||||
* By default, nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
||||
*
|
||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||
* @example [[-1000, -10000], [1000, 1000]]
|
||||
*/
|
||||
nodeExtent?: CoordinateExtent;
|
||||
/** Color of edge markers
|
||||
* @example "#b1b1b7"
|
||||
/**
|
||||
* Color of edge markers.
|
||||
* @default '#b1b1b7'
|
||||
*/
|
||||
defaultMarkerColor?: string;
|
||||
/** Controls if the viewport should zoom by scrolling inside the container */
|
||||
/**
|
||||
* Controls if the viewport should zoom by scrolling inside the container.
|
||||
* @default true
|
||||
*/
|
||||
zoomOnScroll?: boolean;
|
||||
/** Controls if the viewport should zoom by pinching on a touch screen */
|
||||
/**
|
||||
* Controls if the viewport should zoom by pinching on a touch screen.
|
||||
* @default true
|
||||
*/
|
||||
zoomOnPinch?: boolean;
|
||||
/** Controls if the viewport should pan by scrolling inside the container
|
||||
/**
|
||||
* Controls if the viewport should pan by scrolling inside the container.
|
||||
*
|
||||
* Can be limited to a specific direction with panOnScrollMode
|
||||
* Can be limited to a specific direction with `panOnScrollMode`.
|
||||
* @default false
|
||||
*/
|
||||
panOnScroll?: boolean;
|
||||
/** Controls how fast viewport should be panned on scroll.
|
||||
/**
|
||||
* Controls how fast viewport should be panned on scroll.
|
||||
*
|
||||
* Use togther with panOnScroll prop.
|
||||
* Use together with `panOnScroll` prop.
|
||||
* @default 0.5
|
||||
*/
|
||||
panOnScrollSpeed?: number;
|
||||
/** This prop is used to limit the direction of panning when panOnScroll is enabled.
|
||||
/**
|
||||
* This prop is used to limit the direction of panning when `panOnScroll` is enabled.
|
||||
*
|
||||
* The "free" option allows panning in any direction.
|
||||
* The `"free"` option allows panning in any direction.
|
||||
* @default "free"
|
||||
* @example "horizontal" | "vertical"
|
||||
*/
|
||||
panOnScrollMode?: PanOnScrollMode;
|
||||
/** Controls if the viewport should zoom by double clicking somewhere on the flow */
|
||||
/**
|
||||
* Controls if the viewport should zoom by double-clicking somewhere on the flow.
|
||||
* @default true
|
||||
*/
|
||||
zoomOnDoubleClick?: boolean;
|
||||
/**
|
||||
* The radius around an edge connection that can trigger an edge reconnection.
|
||||
* @default 10
|
||||
*/
|
||||
reconnectRadius?: number;
|
||||
/**
|
||||
* If a node is draggable, clicking and dragging that node will move it around the canvas. Adding
|
||||
* the `"nodrag"` class prevents this behavior and this prop allows you to change the name of that
|
||||
* class.
|
||||
* @default "nodrag"
|
||||
*/
|
||||
noDragClassName?: string;
|
||||
/**
|
||||
* Typically, scrolling the mouse wheel when the mouse is over the canvas will zoom the viewport.
|
||||
* Adding the `"nowheel"` class to an element n the canvas will prevent this behavior and this prop
|
||||
* allows you to change the name of that class.
|
||||
* @default "nowheel"
|
||||
*/
|
||||
noWheelClassName?: string;
|
||||
/**
|
||||
* If an element in the canvas does not stop mouse events from propagating, clicking and dragging
|
||||
* that element will pan the viewport. Adding the `"nopan"` class prevents this behavior and this
|
||||
* prop allows you to change the name of that class.
|
||||
* @default "nopan"
|
||||
*/
|
||||
noPanClassName?: string;
|
||||
/** If set, initial viewport will show all nodes & edges */
|
||||
/** When `true`, the flow will be zoomed and panned to fit all the nodes initially provided. */
|
||||
fitView?: boolean;
|
||||
/** Options to be used in combination with fitView
|
||||
/**
|
||||
* When you typically call `fitView` on a `ReactFlowInstance`, you can provide an object of
|
||||
* options to customize its behavior. This prop lets you do the same for the initial `fitView`
|
||||
* call.
|
||||
* @example
|
||||
* const fitViewOptions = {
|
||||
* padding: 0.1,
|
||||
@@ -439,85 +592,105 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* };
|
||||
*/
|
||||
fitViewOptions?: FitViewOptions;
|
||||
/**The connectOnClick option lets you click or tap on a source handle to start a connection
|
||||
/**
|
||||
* The `connectOnClick` option lets you click or tap on a source handle to start a connection
|
||||
* and then click on a target handle to complete the connection.
|
||||
*
|
||||
* If you set this option to false, users will need to drag the connection line to the target
|
||||
* If you set this option to `false`, users will need to drag the connection line to the target
|
||||
* handle to create a connection.
|
||||
* @default true
|
||||
*/
|
||||
connectOnClick?: boolean;
|
||||
/** Set position of the attribution
|
||||
/**
|
||||
* By default, React Flow will render a small attribution in the bottom right corner of the flow.
|
||||
*
|
||||
* You can use this prop to change its position in case you want to place something else there.
|
||||
* @default 'bottom-right'
|
||||
* @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||||
*/
|
||||
attributionPosition?: PanelPosition;
|
||||
/** By default, we render a small attribution in the corner of your flows that links back to the project.
|
||||
/**
|
||||
* By default, we render a small attribution in the corner of your flows that links back to the project.
|
||||
*
|
||||
* Anyone is free to remove this attribution whether they're a Pro subscriber or not
|
||||
* but we ask that you take a quick look at our {@link https://reactflow.dev/learn/troubleshooting/remove-attribution | removing attribution guide}
|
||||
* before doing so.
|
||||
*/
|
||||
proOptions?: ProOptions;
|
||||
/** Enabling this option will raise the z-index of nodes when they are selected.
|
||||
/**
|
||||
* Enabling this option will raise the z-index of nodes when they are selected.
|
||||
* @default true
|
||||
*/
|
||||
elevateNodesOnSelect?: boolean;
|
||||
/** Enabling this option will raise the z-index of edges when they are selected.
|
||||
* @default true
|
||||
/**
|
||||
* Enabling this option will raise the z-index of edges when they are selected.
|
||||
*/
|
||||
elevateEdgesOnSelect?: boolean;
|
||||
/**
|
||||
* Can be set true if built-in keyboard controls should be disabled.
|
||||
* You can use this prop to disable keyboard accessibility features such as selecting nodes or
|
||||
* moving selected nodes with the arrow keys.
|
||||
* @default false
|
||||
*/
|
||||
disableKeyboardA11y?: boolean;
|
||||
/** You can enable this prop to automatically pan the viewport while dragging a node.
|
||||
/**
|
||||
* When `true`, the viewport will pan automatically when the cursor moves to the edge of the
|
||||
* viewport while dragging a node.
|
||||
* @default true
|
||||
*/
|
||||
autoPanOnNodeDrag?: boolean;
|
||||
/** You can enable this prop to automatically pan the viewport while dragging a node.
|
||||
/**
|
||||
* When `true`, the viewport will pan automatically when the cursor moves to the edge of the
|
||||
* viewport while creating a connection.
|
||||
* @default true
|
||||
*/
|
||||
autoPanOnConnect?: boolean;
|
||||
/** The speed at which the viewport pans while dragging a node or a selection box.
|
||||
/**
|
||||
* The speed at which the viewport pans while dragging a node or a selection box.
|
||||
* @default 15
|
||||
*/
|
||||
autoPanSpeed?: number;
|
||||
/** You can enable this prop to automatically pan the viewport while making a new connection.
|
||||
* @default true
|
||||
/**
|
||||
* The radius around a handle where you drop a connection line to create a new edge.
|
||||
* @default 20
|
||||
*/
|
||||
connectionRadius?: number;
|
||||
/** Ocassionally something may happen that causes Svelte Flow to throw an error.
|
||||
/**
|
||||
* Occasionally something may happen that causes React Flow to throw an error.
|
||||
*
|
||||
* Instead of exploding your application, we log a message to the console and then call this event handler.
|
||||
* You might use it for additional logging or to show a message to the user.
|
||||
*/
|
||||
onError?: OnError;
|
||||
/** This callback can be used to validate a new connection
|
||||
/**
|
||||
* This callback can be used to validate a new connection
|
||||
*
|
||||
* If you return false, the edge will not be added to your flow.
|
||||
* If you have custom connection logic its preferred to use this callback over the isValidConnection prop on the handle component for performance reasons.
|
||||
* @default (connection: Connection) => true
|
||||
* If you return `false`, the edge will not be added to your flow.
|
||||
* If you have custom connection logic its preferred to use this callback over the
|
||||
* `isValidConnection` prop on the handle component for performance reasons.
|
||||
*/
|
||||
isValidConnection?: IsValidConnection<EdgeType>;
|
||||
/** With a threshold greater than zero you can control the distinction between node drag and click events.
|
||||
/**
|
||||
* With a threshold greater than zero you can delay node drag events.
|
||||
*
|
||||
* If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired.
|
||||
*
|
||||
* 1 is the default value, so clicks don't trigger drag events.
|
||||
* @default 1
|
||||
*/
|
||||
nodeDragThreshold?: number;
|
||||
/** Sets a fixed width for the flow */
|
||||
/** Sets a fixed width for the flow. */
|
||||
width?: number;
|
||||
/** Sets a fixed height for the flow */
|
||||
/** Sets a fixed height for the flow. */
|
||||
height?: number;
|
||||
/** Controls color scheme used for styling the flow
|
||||
* @default 'system'
|
||||
/**
|
||||
* Controls color scheme used for styling the flow.
|
||||
* @default 'light'
|
||||
* @example 'system' | 'light' | 'dark'
|
||||
*/
|
||||
colorMode?: ColorMode;
|
||||
/** If set true, some debug information will be logged to the console like which events are fired.
|
||||
*
|
||||
* @default undefined
|
||||
/**
|
||||
* If set `true`, some debug information will be logged to the console like which events are fired.
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,18 @@ import type {
|
||||
|
||||
import { EdgeTypes, InternalNode, Node } from '.';
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type EdgeLabelOptions = {
|
||||
label?: string | ReactNode;
|
||||
/**
|
||||
* The label or custom element to render along the edge. This is commonly a text label or some
|
||||
* custom controls.
|
||||
*/
|
||||
label?: ReactNode;
|
||||
/**
|
||||
* Custom styles to apply to the label.
|
||||
*/
|
||||
labelStyle?: CSSProperties;
|
||||
labelShowBg?: boolean;
|
||||
labelBgStyle?: CSSProperties;
|
||||
@@ -29,7 +39,8 @@ export type EdgeLabelOptions = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The Edge type is mainly used for the `edges` that get passed to the ReactFlow component
|
||||
* An `Edge` is the complete description with everything React Flow needs
|
||||
* to know in order to render it.
|
||||
* @public
|
||||
*/
|
||||
export type Edge<
|
||||
@@ -39,6 +50,11 @@ export type Edge<
|
||||
EdgeLabelOptions & {
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
/**
|
||||
* Determines whether the edge can be updated by dragging the source or target to a new node.
|
||||
* This property will override the default set by the `edgesReconnectable` prop on the
|
||||
* `<ReactFlow />` component.
|
||||
*/
|
||||
reconnectable?: boolean | HandleType;
|
||||
focusable?: boolean;
|
||||
};
|
||||
@@ -91,17 +107,26 @@ export type EdgeWrapperProps<EdgeType extends Edge = Edge> = {
|
||||
disableKeyboardA11y?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Many properties on an [`Edge`](/api-reference/types/edge) are optional. When a new edge is created,
|
||||
* the properties that are not provided will be filled in with the default values
|
||||
* passed to the `defaultEdgeOptions` prop of the [`<ReactFlow />`](/api-reference/react-flow#defaultedgeoptions) component.
|
||||
*/
|
||||
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
||||
|
||||
export type EdgeTextProps = SVGAttributes<SVGElement> &
|
||||
export type EdgeTextProps = Omit<SVGAttributes<SVGElement>, 'x' | 'y'> &
|
||||
EdgeLabelOptions & {
|
||||
/** The x position where the label should be rendered. */
|
||||
x: number;
|
||||
/** The y position where the label should be rendered. */
|
||||
y: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom edge component props
|
||||
* When you implement a custom edge it is wrapped in a component that enables some
|
||||
* basic functionality. The `EdgeProps` type is the props that are passed to this.
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
||||
EdgeType,
|
||||
@@ -121,22 +146,42 @@ export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
||||
/**
|
||||
* BaseEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd'> &
|
||||
export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd' | 'path' | 'markerStart' | 'markerEnd'> &
|
||||
EdgeLabelOptions & {
|
||||
/** Additional padding where interacting with an edge is still possible */
|
||||
/**
|
||||
* The width of the invisible area around the edge that the user can interact with. This is
|
||||
* useful for making the edge easier to click or hover over.
|
||||
* @default 20
|
||||
*/
|
||||
interactionWidth?: number;
|
||||
/** The x position of edge label */
|
||||
labelX?: number;
|
||||
/** The y position of edge label */
|
||||
labelY?: number;
|
||||
/** SVG path of the edge */
|
||||
/**
|
||||
* The SVG path string that defines the edge. This should look something like
|
||||
* `'M 0 0 L 100 100'` for a simple line. The utility functions like `getSimpleBezierEdge` can
|
||||
* be used to generate this string for you.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* The id of the SVG marker to use at the start of the edge. This should be defined in a
|
||||
* `<defs>` element in a separate SVG document or element.
|
||||
*/
|
||||
markerStart?: string;
|
||||
/**
|
||||
* The id of the SVG marker to use at the end of the edge. This should be defined in a `<defs>`
|
||||
* element in a separate SVG document or element.
|
||||
*/
|
||||
markerEnd?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper type for edge components that get exported by the library
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type EdgeComponentProps = EdgePosition &
|
||||
EdgeLabelOptions & {
|
||||
@@ -156,39 +201,53 @@ export type EdgeComponentWithPathOptions<PathOptions> = EdgeComponentProps & {
|
||||
/**
|
||||
* BezierEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
|
||||
|
||||
/**
|
||||
* SmoothStepEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
|
||||
|
||||
/**
|
||||
* StepEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
|
||||
|
||||
/**
|
||||
* StraightEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
|
||||
|
||||
/**
|
||||
* SimpleBezier component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type SimpleBezierEdgeProps = EdgeComponentProps;
|
||||
|
||||
export type OnReconnect<EdgeType extends Edge = Edge> = (oldEdge: EdgeType, newConnection: Connection) => void;
|
||||
|
||||
/**
|
||||
* If you want to render a custom component for connection lines, you can set the
|
||||
* `connectionLineComponent` prop on the [`<ReactFlow />`](/api-reference/react-flow#connection-connectionLineComponent)
|
||||
* component. The `ConnectionLineComponentProps` are passed to your custom component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ConnectionLineComponentProps<NodeType extends Node = Node> = {
|
||||
connectionLineStyle?: CSSProperties;
|
||||
connectionLineType: ConnectionLineType;
|
||||
/** The node the connection line originates from. */
|
||||
fromNode: InternalNode<NodeType>;
|
||||
/** The handle on the `fromNode` that the connection line originates from. */
|
||||
fromHandle: Handle;
|
||||
fromX: number;
|
||||
fromY: number;
|
||||
@@ -196,6 +255,10 @@ export type ConnectionLineComponentProps<NodeType extends Node = Node> = {
|
||||
toY: number;
|
||||
fromPosition: Position;
|
||||
toPosition: Position;
|
||||
/**
|
||||
* If there is an `isValidConnection` callback, this prop will be set to `"valid"` or `"invalid"`
|
||||
* based on the return value of that callback. Otherwise, it will be `null`.
|
||||
*/
|
||||
connectionStatus: 'valid' | 'invalid' | null;
|
||||
toNode: InternalNode<NodeType> | null;
|
||||
toHandle: Handle | null;
|
||||
|
||||
@@ -18,11 +18,44 @@ import {
|
||||
|
||||
import type { Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.';
|
||||
|
||||
/**
|
||||
* This type can be used to type the `onNodesChange` function with a custom node type.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const onNodesChange: OnNodesChange<MyNodeType> = useCallback((changes) => {
|
||||
* setNodes((nodes) => applyNodeChanges(nodes, changes));
|
||||
* },[]);
|
||||
* ```
|
||||
*/
|
||||
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
||||
|
||||
/**
|
||||
* This type can be used to type the `onEdgesChange` function with a custom edge type.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* const onEdgesChange: OnEdgesChange<MyEdgeType> = useCallback((changes) => {
|
||||
* setEdges((edges) => applyEdgeChanges(edges, changes));
|
||||
* },[]);
|
||||
* ```
|
||||
*/
|
||||
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
||||
|
||||
export type OnNodesDelete<NodeType extends Node = Node> = (nodes: NodeType[]) => void;
|
||||
export type OnEdgesDelete<EdgeType extends Edge = Edge> = (edges: EdgeType[]) => void;
|
||||
|
||||
/**
|
||||
* This type can be used to type the `onDelete` function with a custom node and edge type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type OnDelete<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (params: {
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
@@ -39,6 +72,7 @@ export type NodeTypes = Record<
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
export type EdgeTypes = Record<
|
||||
string,
|
||||
ComponentType<
|
||||
@@ -51,25 +85,38 @@ export type EdgeTypes = Record<
|
||||
>
|
||||
>;
|
||||
|
||||
export type UnselectNodesAndEdgesParams = {
|
||||
nodes?: Node[];
|
||||
edges?: Edge[];
|
||||
export type UnselectNodesAndEdgesParams<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
nodes?: NodeType[];
|
||||
edges?: EdgeType[];
|
||||
};
|
||||
|
||||
export type OnSelectionChangeParams = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
export type OnSelectionChangeParams<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
};
|
||||
|
||||
export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
|
||||
export type OnSelectionChangeFunc<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
||||
params: OnSelectionChangeParams<NodeType, EdgeType>
|
||||
) => void;
|
||||
|
||||
export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>;
|
||||
|
||||
/**
|
||||
* When calling [`fitView`](/api-reference/types/react-flow-instance#fitview) these options
|
||||
* can be used to customize the behaviour. For example, the `duration` option can be used to
|
||||
* transform the viewport smoothly over a given amount of time.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
|
||||
export type FitView = (fitViewOptions?: FitViewOptions) => Promise<boolean>;
|
||||
export type FitView<NodeType extends Node = Node> = (fitViewOptions?: FitViewOptions<NodeType>) => Promise<boolean>;
|
||||
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
||||
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type ViewportHelperFunctions = {
|
||||
/**
|
||||
* Zooms viewport in by 1.2.
|
||||
@@ -84,14 +131,15 @@ export type ViewportHelperFunctions = {
|
||||
*/
|
||||
zoomOut: ZoomInOut;
|
||||
/**
|
||||
* Sets the current zoom level.
|
||||
* Zoom the viewport to a given zoom level. Passing in a `duration` will animate the viewport to
|
||||
* the new zoom level.
|
||||
*
|
||||
* @param zoomLevel - the zoom level to set
|
||||
* @param options.duration - optional duration. If set, a transition will be applied
|
||||
*/
|
||||
zoomTo: ZoomTo;
|
||||
/**
|
||||
* Returns the current zoom level.
|
||||
* Get the current zoom level of the viewport.
|
||||
*
|
||||
* @returns current zoom as a number
|
||||
*/
|
||||
@@ -110,18 +158,8 @@ export type ViewportHelperFunctions = {
|
||||
*/
|
||||
getViewport: GetViewport;
|
||||
/**
|
||||
* Fits the view.
|
||||
*
|
||||
* @param options.padding - optional padding
|
||||
* @param options.includeHiddenNodes - optional includeHiddenNodes
|
||||
* @param options.minZoom - optional minZoom
|
||||
* @param options.maxZoom - optional maxZoom
|
||||
* @param options.duration - optional duration. If set, a transition will be applied
|
||||
* @param options.nodes - optional nodes to fit the view to
|
||||
*/
|
||||
fitView: FitView;
|
||||
/**
|
||||
* Sets the center of the view to the given position.
|
||||
* Center the viewport on a given position. Passing in a `duration` will animate the viewport to
|
||||
* the new position.
|
||||
*
|
||||
* @param x - x position
|
||||
* @param y - y position
|
||||
@@ -129,14 +167,17 @@ export type ViewportHelperFunctions = {
|
||||
*/
|
||||
setCenter: SetCenter;
|
||||
/**
|
||||
* Fits the view to the given bounds .
|
||||
* A low-level utility function to fit the viewport to a given rectangle. By passing in a
|
||||
* `duration`, the viewport will animate from its current position to the new position. The
|
||||
* `padding` option can be used to add space around the bounds.
|
||||
*
|
||||
* @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to
|
||||
* @param options.padding - optional padding
|
||||
*/
|
||||
fitBounds: FitBounds;
|
||||
/**
|
||||
* Converts a screen / client position to a flow position.
|
||||
* With this function you can translate a screen pixel position to a flow position. It is useful
|
||||
* for implementing drag and drop from a sidebar for example.
|
||||
*
|
||||
* @param clientPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY
|
||||
* @param options.snapToGrid - if true, the converted position will be snapped to the grid
|
||||
@@ -147,7 +188,7 @@ export type ViewportHelperFunctions = {
|
||||
*/
|
||||
screenToFlowPosition: (clientPosition: XYPosition, options?: { snapToGrid: boolean }) => XYPosition;
|
||||
/**
|
||||
* Converts a flow position to a screen / client position.
|
||||
* Translate a position inside the flow's canvas to a screen pixel position.
|
||||
*
|
||||
* @param flowPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY
|
||||
* @returns position as { x: number, y: number }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
import type { HandleConnection, HandleType, NodeConnection, Rect, Viewport } from '@xyflow/system';
|
||||
import type { Node, Edge, ViewportHelperFunctions, InternalNode } from '.';
|
||||
import type { Node, Edge, ViewportHelperFunctions, InternalNode, FitView } from '.';
|
||||
|
||||
export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
nodes: NodeType[];
|
||||
@@ -13,6 +13,9 @@ export type DeleteElementsOptions = {
|
||||
edges?: (Edge | { id: Edge['id'] })[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
/**
|
||||
* Returns nodes.
|
||||
@@ -21,13 +24,17 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
*/
|
||||
getNodes: () => NodeType[];
|
||||
/**
|
||||
* Sets nodes.
|
||||
* Set your nodes array to something else by either overwriting it with a new array or by passing
|
||||
* in a function to update the existing array. If using a function, it is important to make sure a
|
||||
* new array is returned instead of mutating the existing array. Calling this function will
|
||||
* trigger the `onNodesChange` handler in a controlled flow.
|
||||
*
|
||||
* @param payload - the nodes to set or a function that receives the current nodes and returns the new nodes
|
||||
*/
|
||||
setNodes: (payload: NodeType[] | ((nodes: NodeType[]) => NodeType[])) => void;
|
||||
/**
|
||||
* Adds nodes.
|
||||
* Add one or many nodes to your existing nodes array. Calling this function will trigger the
|
||||
* `onNodesChange` handler in a controlled flow.
|
||||
*
|
||||
* @param payload - the nodes to add
|
||||
*/
|
||||
@@ -53,13 +60,17 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
*/
|
||||
getEdges: () => EdgeType[];
|
||||
/**
|
||||
* Sets edges.
|
||||
* Set your edges array to something else by either overwriting it with a new array or by passing
|
||||
* in a function to update the existing array. If using a function, it is important to make sure a
|
||||
* new array is returned instead of mutating the existing array. Calling this function will
|
||||
* trigger the `onEdgesChange` handler in a controlled flow.
|
||||
*
|
||||
* @param payload - the edges to set or a function that receives the current edges and returns the new edges
|
||||
*/
|
||||
setEdges: (payload: EdgeType[] | ((edges: EdgeType[]) => EdgeType[])) => void;
|
||||
/**
|
||||
* Adds edges.
|
||||
* Add one or many edges to your existing edges array. Calling this function will trigger the
|
||||
* `onEdgesChange` handler in a controlled flow.
|
||||
*
|
||||
* @param payload - the edges to add
|
||||
*/
|
||||
@@ -90,7 +101,8 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
deletedEdges: Edge[];
|
||||
}>;
|
||||
/**
|
||||
* Returns all nodes that intersect with the given node or rect.
|
||||
* Find all the nodes currently intersecting with a given node or rectangle. The `partially`
|
||||
* parameter can be set to `true` to include nodes that are only partially intersecting.
|
||||
*
|
||||
* @param node - the node or rect to check for intersections
|
||||
* @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed node or rect
|
||||
@@ -104,7 +116,8 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
nodes?: NodeType[]
|
||||
) => NodeType[];
|
||||
/**
|
||||
* Checks if the given node or rect intersects with the passed rect.
|
||||
* Determine if a given node or rectangle is intersecting with another rectangle. The `partially`
|
||||
* parameter can be set to true return `true` even if the node is only partially intersecting.
|
||||
*
|
||||
* @param node - the node or rect to check for intersections
|
||||
* @param area - the rect to check for intersections
|
||||
@@ -182,7 +195,8 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
*/
|
||||
getNodesBounds: (nodes: (NodeType | InternalNode | string)[]) => Rect;
|
||||
/**
|
||||
* Gets all connections for a given handle belonging to a specific node.
|
||||
* Get all the connections of a handle belonging to a specific node. The type parameter be either
|
||||
* `'source'` or `'target'`.
|
||||
* @deprecated
|
||||
* @param type - handle type 'source' or 'target'
|
||||
* @param id - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle)
|
||||
@@ -200,7 +214,6 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
}) => HandleConnection[];
|
||||
/**
|
||||
* Gets all connections to a node. Can be filtered by handle type and id.
|
||||
* @deprecated use `getNodeConnections` instead
|
||||
* @param type - handle type 'source' or 'target'
|
||||
* @param handleId - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle)
|
||||
* @param nodeId - the node id the handle belongs to
|
||||
@@ -215,12 +228,34 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
nodeId: string;
|
||||
handleId?: string | null;
|
||||
}) => NodeConnection[];
|
||||
// /**
|
||||
// * Fits the view.
|
||||
// *
|
||||
// * @param options.padding - optional padding
|
||||
// * @param options.includeHiddenNodes - optional includeHiddenNodes
|
||||
// * @param options.minZoom - optional minZoom
|
||||
// * @param options.maxZoom - optional maxZoom
|
||||
// * @param options.duration - optional duration. If set, a transition will be applied
|
||||
// * @param options.nodes - optional nodes to fit the view to
|
||||
// */
|
||||
fitView: FitView<NodeType>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `ReactFlowInstance` provides a collection of methods to query and manipulate
|
||||
* the internal state of your flow. You can get an instance by using the
|
||||
* [`useReactFlow`](/api-reference/hooks/use-react-flow) hook or attaching a listener
|
||||
* to the [`onInit`](/api-reference/react-flow#event-oninit) event.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = GeneralHelpers<
|
||||
NodeType,
|
||||
EdgeType
|
||||
> &
|
||||
Omit<ViewportHelperFunctions, 'initialized'> & {
|
||||
ViewportHelperFunctions & {
|
||||
/**
|
||||
* React Flow needs to mount the viewport to the DOM and initialize its zoom and pan behavior.
|
||||
* This property tells you when viewport is initialized.
|
||||
*/
|
||||
viewportInitialized: boolean;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,10 @@ import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, I
|
||||
import { NodeTypes } from './general';
|
||||
|
||||
/**
|
||||
* The node data structure that gets used for the nodes prop.
|
||||
* The `Node` type represents everything React Flow needs to know about a given node.
|
||||
* Whenever you want to update a certain attribute of a node, you need to create a new
|
||||
* node object.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Node<
|
||||
@@ -18,9 +21,10 @@ export type Node<
|
||||
};
|
||||
|
||||
/**
|
||||
* The node data structure that gets used for internal nodes.
|
||||
* There are some data structures added under node.internal
|
||||
* that are needed for tracking some properties
|
||||
* The `InternalNode` type is identical to the base [`Node`](/api-references/types/node)
|
||||
* type but is extended with some additional properties used internally.
|
||||
* Some functions and callbacks that return nodes may return an `InternalNode`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
||||
@@ -56,8 +60,46 @@ export type NodeWrapperProps<NodeType extends Node> = {
|
||||
nodeClickDistance?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `BuiltInNode` type represents the built-in node types that are available in React Flow.
|
||||
* You can use this type to extend your custom node type if you still want ot use the built-in ones.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* ```ts
|
||||
* type CustomNode = Node<{ value: number }, 'custom'>;
|
||||
* type MyAppNode = CustomNode | BuiltInNode;
|
||||
* ```
|
||||
*/
|
||||
export type BuiltInNode =
|
||||
| Node<{ label: string }, 'input' | 'output' | 'default'>
|
||||
| Node<Record<string, never>, 'group'>;
|
||||
|
||||
/**
|
||||
* When you implement a [custom node](/learn/customization/custom-nodes) it is
|
||||
* wrapped in a component that enables basic functionality like selection and
|
||||
* dragging. Your custom node receives `NodeProps` as props.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useState } from 'react';
|
||||
*import { NodeProps, Node } from '@xyflow/react';
|
||||
*
|
||||
*export type CounterNode = Node<{ initialCount?: number }, 'counter'>;
|
||||
*
|
||||
*export default function CounterNode(props: NodeProps<CounterNode>) {
|
||||
* const [count, setCount] = useState(props.data?.initialCount ?? 0);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>Count: {count}</p>
|
||||
* <button className="nodrag" onClick={() => setCount(count + 1)}>
|
||||
* Increment
|
||||
* </button>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export type NodeProps<NodeType extends Node = Node> = NodePropsBase<NodeType>;
|
||||
|
||||
@@ -119,9 +119,9 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
connectOnClick: boolean;
|
||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||
|
||||
fitViewOnInit: boolean;
|
||||
fitViewDone: boolean;
|
||||
fitViewOnInitOptions: FitViewOptions | undefined;
|
||||
fitViewQueued: boolean;
|
||||
fitViewOptions: FitViewOptions | undefined;
|
||||
fitViewResolver: PromiseWithResolvers<boolean> | null;
|
||||
|
||||
onNodesDelete?: OnNodesDelete<NodeType>;
|
||||
onEdgesDelete?: OnEdgesDelete<EdgeType>;
|
||||
@@ -134,7 +134,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
onViewportChangeEnd?: OnViewportChange;
|
||||
onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>;
|
||||
|
||||
onSelectionChangeHandlers: OnSelectionChangeFunc[];
|
||||
onSelectionChangeHandlers: OnSelectionChangeFunc<NodeType, EdgeType>[];
|
||||
|
||||
ariaLiveMessage: string;
|
||||
autoPanOnConnect: boolean;
|
||||
@@ -155,7 +155,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||
updateNodeInternals: (updates: Map<string, InternalNodeUpdate>, params?: { triggerFitView: boolean }) => void;
|
||||
updateNodePositions: UpdateNodePositions;
|
||||
resetSelectedElements: () => void;
|
||||
unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void;
|
||||
unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams<NodeType, EdgeType>) => void;
|
||||
addSelectedNodes: (nodeIds: string[]) => void;
|
||||
addSelectedEdges: (edgeIds: string[]) => void;
|
||||
setMinZoom: (minZoom: number) => void;
|
||||
@@ -168,8 +168,6 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
||||
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
||||
panBy: PanBy;
|
||||
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
||||
fitViewSync: (options?: FitViewOptions) => boolean;
|
||||
setPaneClickDistance: (distance: number) => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,13 +11,17 @@ import {
|
||||
} from '@xyflow/system';
|
||||
import type { Node, Edge, InternalNode } from '../types';
|
||||
|
||||
// This function applies changes to nodes or edges that are triggered by React Flow internally.
|
||||
// When you drag a node for example, React Flow will send a position change update.
|
||||
// This function then applies the changes and returns the updated elements.
|
||||
/*
|
||||
* This function applies changes to nodes or edges that are triggered by React Flow internally.
|
||||
* When you drag a node for example, React Flow will send a position change update.
|
||||
* This function then applies the changes and returns the updated elements.
|
||||
*/
|
||||
function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
const updatedElements: any[] = [];
|
||||
// By storing a map of changes for each element, we can a quick lookup as we
|
||||
// iterate over the elements array!
|
||||
/*
|
||||
* By storing a map of changes for each element, we can a quick lookup as we
|
||||
* iterate over the elements array!
|
||||
*/
|
||||
const changesMap = new Map<any, any[]>();
|
||||
const addItemChanges: any[] = [];
|
||||
|
||||
@@ -26,15 +30,19 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
addItemChanges.push(change);
|
||||
continue;
|
||||
} else if (change.type === 'remove' || change.type === 'replace') {
|
||||
// For a 'remove' change we can safely ignore any other changes queued for
|
||||
// the same element, it's going to be removed anyway!
|
||||
/*
|
||||
* For a 'remove' change we can safely ignore any other changes queued for
|
||||
* the same element, it's going to be removed anyway!
|
||||
*/
|
||||
changesMap.set(change.id, [change]);
|
||||
} else {
|
||||
const elementChanges = changesMap.get(change.id);
|
||||
|
||||
if (elementChanges) {
|
||||
// If we have some changes queued already, we can do a mutable update of
|
||||
// that array and save ourselves some copying.
|
||||
/*
|
||||
* If we have some changes queued already, we can do a mutable update of
|
||||
* that array and save ourselves some copying.
|
||||
*/
|
||||
elementChanges.push(change);
|
||||
} else {
|
||||
changesMap.set(change.id, [change]);
|
||||
@@ -45,8 +53,10 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
for (const element of elements) {
|
||||
const changes = changesMap.get(element.id);
|
||||
|
||||
// When there are no changes for an element we can just push it unmodified,
|
||||
// no need to copy it.
|
||||
/*
|
||||
* When there are no changes for an element we can just push it unmodified,
|
||||
* no need to copy it.
|
||||
*/
|
||||
if (!changes) {
|
||||
updatedElements.push(element);
|
||||
continue;
|
||||
@@ -62,9 +72,11 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
continue;
|
||||
}
|
||||
|
||||
// For other types of changes, we want to start with a shallow copy of the
|
||||
// object so React knows this element has changed. Sequential changes will
|
||||
/// each _mutate_ this object, so there's only ever one copy.
|
||||
/**
|
||||
* For other types of changes, we want to start with a shallow copy of the
|
||||
* object so React knows this element has changed. Sequential changes will
|
||||
* each _mutate_ this object, so there's only ever one copy.
|
||||
*/
|
||||
const updatedElement = { ...element };
|
||||
|
||||
for (const change of changes) {
|
||||
@@ -74,8 +86,10 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
updatedElements.push(updatedElement);
|
||||
}
|
||||
|
||||
// we need to wait for all changes to be applied before adding new items
|
||||
// to be able to add them at the correct index
|
||||
/*
|
||||
* we need to wait for all changes to be applied before adding new items
|
||||
* to be able to add them at the correct index
|
||||
*/
|
||||
if (addItemChanges.length) {
|
||||
addItemChanges.forEach((change) => {
|
||||
if (change.index !== undefined) {
|
||||
@@ -133,22 +147,33 @@ function applyChange(change: any, element: any): any {
|
||||
/**
|
||||
* Drop in function that applies node changes to an array of nodes.
|
||||
* @public
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
|
||||
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
||||
* @param changes - Array of changes to apply
|
||||
* @param nodes - Array of nodes to apply the changes to
|
||||
* @returns Array of updated nodes
|
||||
* @param changes - Array of changes to apply.
|
||||
* @param nodes - Array of nodes to apply the changes to.
|
||||
* @returns Array of updated nodes.
|
||||
* @example
|
||||
* const onNodesChange = useCallback(
|
||||
(changes) => {
|
||||
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
||||
},
|
||||
[setNodes],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
||||
);
|
||||
*```tsx
|
||||
*import { useState, useCallback } from 'react';
|
||||
*import { ReactFlow, applyNodeChanges, type Node, type Edge, type OnNodesChange } from '@xyflow/react';
|
||||
*
|
||||
*export default function Flow() {
|
||||
* const [nodes, setNodes] = useState<Node[]>([]);
|
||||
* const [edges, setEdges] = useState<Edge[]>([]);
|
||||
* const onNodesChange: OnNodesChange = useCallback(
|
||||
* (changes) => {
|
||||
* setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
||||
* },
|
||||
* [setNodes],
|
||||
* );
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange}
|
||||
* that describes how to update the edges of your flow in some way.
|
||||
* If you don't need any custom behaviour, this util can be used to take an array
|
||||
* of these changes and apply them to your edges.
|
||||
*/
|
||||
export function applyNodeChanges<NodeType extends Node = Node>(
|
||||
changes: NodeChange<NodeType>[],
|
||||
@@ -160,22 +185,33 @@ export function applyNodeChanges<NodeType extends Node = Node>(
|
||||
/**
|
||||
* Drop in function that applies edge changes to an array of edges.
|
||||
* @public
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
|
||||
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
||||
* @param changes - Array of changes to apply
|
||||
* @param edges - Array of edge to apply the changes to
|
||||
* @returns Array of updated edges
|
||||
* @param changes - Array of changes to apply.
|
||||
* @param edges - Array of edge to apply the changes to.
|
||||
* @returns Array of updated edges.
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useState, useCallback } from 'react';
|
||||
*import { ReactFlow, applyEdgeChanges } from '@xyflow/react';
|
||||
*
|
||||
*export default function Flow() {
|
||||
* const [nodes, setNodes] = useState([]);
|
||||
* const [edges, setEdges] = useState([]);
|
||||
* const onEdgesChange = useCallback(
|
||||
(changes) => {
|
||||
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
||||
);
|
||||
* (changes) => {
|
||||
* setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
||||
* },
|
||||
* [setEdges],
|
||||
* );
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange}
|
||||
* that describes how to update the edges of your flow in some way.
|
||||
* If you don't need any custom behaviour, this util can be used to take an array
|
||||
* of these changes and apply them to your edges.
|
||||
*/
|
||||
export function applyEdgeChanges<EdgeType extends Edge = Edge>(
|
||||
changes: EdgeChange<EdgeType>[],
|
||||
@@ -205,9 +241,11 @@ export function getSelectionChanges(
|
||||
// we don't want to set all items to selected=false on the first selection
|
||||
if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
|
||||
if (mutateItem) {
|
||||
// this hack is needed for nodes. When the user dragged a node, it's selected.
|
||||
// When another node gets dragged, we need to deselect the previous one,
|
||||
// in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
||||
/*
|
||||
* this hack is needed for nodes. When the user dragged a node, it's selected.
|
||||
* When another node gets dragged, we need to deselect the previous one,
|
||||
* in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
||||
*/
|
||||
item.selected = willBeSelected;
|
||||
}
|
||||
changes.push(createSelectionChange(item.id, willBeSelected));
|
||||
|
||||
@@ -1,29 +1,57 @@
|
||||
import { type Ref, type RefAttributes, forwardRef } from 'react';
|
||||
import { type Ref, type RefAttributes, forwardRef, JSX } from 'react';
|
||||
import { isNodeBase, isEdgeBase } from '@xyflow/system';
|
||||
|
||||
import type { Edge, Node } from '../types';
|
||||
|
||||
/**
|
||||
* Test whether an object is useable as a Node
|
||||
* Test whether an object is usable as an [`Node`](/api-reference/types/node).
|
||||
* In TypeScript this is a type guard that will narrow the type of whatever you pass in to
|
||||
* [`Node`](/api-reference/types/node) if it returns `true`.
|
||||
*
|
||||
* @public
|
||||
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
|
||||
* @param element - The element to test
|
||||
* @returns A boolean indicating whether the element is an Node
|
||||
* @param element - The element to test.
|
||||
* @returns Tests whether the provided value can be used as a `Node`. If you're using TypeScript,
|
||||
* this function acts as a type guard and will narrow the type of the value to `Node` if it returns
|
||||
* `true`.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { isNode } from '@xyflow/react';
|
||||
*
|
||||
*if (isNode(node)) {
|
||||
* // ...
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const isNode = <NodeType extends Node = Node>(element: unknown): element is NodeType =>
|
||||
isNodeBase<NodeType>(element);
|
||||
|
||||
/**
|
||||
* Test whether an object is useable as an Edge
|
||||
* Test whether an object is usable as an [`Edge`](/api-reference/types/edge).
|
||||
* In TypeScript this is a type guard that will narrow the type of whatever you pass in to
|
||||
* [`Edge`](/api-reference/types/edge) if it returns `true`.
|
||||
*
|
||||
* @public
|
||||
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
|
||||
* @param element - The element to test
|
||||
* @returns A boolean indicating whether the element is an Edge
|
||||
* @returns Tests whether the provided value can be used as an `Edge`. If you're using TypeScript,
|
||||
* this function acts as a type guard and will narrow the type of the value to `Edge` if it returns
|
||||
* `true`.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { isEdge } from '@xyflow/react';
|
||||
*
|
||||
*if (isEdge(edge)) {
|
||||
* // ...
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
|
||||
isEdgeBase<EdgeType>(element);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export function fixedForwardRef<T, P = {}>(
|
||||
render: (props: P, ref: Ref<T>) => JSX.Element
|
||||
): (props: P & RefAttributes<T>) => JSX.Element {
|
||||
|
||||
@@ -1,5 +1,60 @@
|
||||
# @xyflow/svelte
|
||||
|
||||
## 0.1.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5158](https://github.com/xyflow/xyflow/pull/5158) [`06696060`](https://github.com/xyflow/xyflow/commit/0669606050bb2138a44a1591176ac8e16afeb0f1) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Fix typo in TSDoc comments `React Flow` -> `Svelte Flow`
|
||||
|
||||
- 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
|
||||
|
||||
## 0.1.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5139](https://github.com/xyflow/xyflow/pull/5139) [`89de9ca8`](https://github.com/xyflow/xyflow/commit/89de9ca83fbf9263a687a0f5f915efb2beb31654) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Use `rgba` for colors with opacity instead of `rgb` for `MiniMap` mask color
|
||||
|
||||
- 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
|
||||
|
||||
## 0.1.33
|
||||
|
||||
### 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
|
||||
|
||||
## 0.1.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#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)
|
||||
|
||||
- [#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
|
||||
|
||||
- [#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
|
||||
|
||||
- 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
|
||||
|
||||
## 0.1.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5019](https://github.com/xyflow/xyflow/pull/5019) [`3e80317c`](https://github.com/xyflow/xyflow/commit/3e80317cf6da0e9fdc111c3ade88f2a88a10dbd6) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Add `"./package.json" to the `exports` field so that users can import it
|
||||
|
||||
- Updated dependencies [[`99dd7d35`](https://github.com/xyflow/xyflow/commit/99dd7d3549e7423e7d103b2c956c8b37f5747b90)]:
|
||||
- @xyflow/system@0.0.52
|
||||
|
||||
## 0.1.30
|
||||
|
||||
### 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
|
||||
|
||||
- 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
|
||||
|
||||
## 0.1.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"type": "module",
|
||||
"module": "./dist/lib/index.js",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./dist/lib/index.d.ts",
|
||||
"svelte": "./dist/lib/index.js",
|
||||
@@ -54,32 +55,32 @@
|
||||
"@xyflow/system": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/kit": "^2.16.1",
|
||||
"@sveltejs/package": "^2.3.9",
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.20.4",
|
||||
"@sveltejs/package": "^2.3.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.22.0",
|
||||
"@typescript-eslint/parser": "^8.22.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cssnano": "^7.0.6",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-svelte": "^3.5.1",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-combine-duplicated-selectors": "^10.0.3",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-nested": "^7.0.2",
|
||||
"postcss-rename": "^0.6.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.19.5",
|
||||
"svelte-check": "^4.1.4",
|
||||
"svelte-eslint-parser": "^0.43.0",
|
||||
"svelte": "^5.25.8",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte-eslint-parser": "^1.1.2",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.3"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.16.0"
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
|
||||
function onClick(event: MouseEvent) {
|
||||
// We prevent click events when the user let go of the selectionKey during a selection
|
||||
if (selectionInProgress) {
|
||||
// We also prevent click events when a connection is in progress
|
||||
if (selectionInProgress || store.connection.inProgress) {
|
||||
selectionInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let { store, children }: { store: SvelteFlowStore; children: Snippet } = $props();
|
||||
|
||||
let { x, y, zoom } = $derived(store._viewport);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="svelte-flow__viewport xyflow__viewport"
|
||||
style="transform: translate({store.viewport.x}px, {store.viewport.y}px) scale({store.viewport
|
||||
.zoom})"
|
||||
style="transform: translate({x}px, {y}px) scale({zoom})"
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { PanOnScrollMode, type PanZoomInstance, type Transform } from '@xyflow/system';
|
||||
|
||||
import zoom from '$lib/actions/zoom';
|
||||
@@ -24,15 +23,8 @@
|
||||
let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag);
|
||||
let panOnScrollActive = $derived(store.panActivationKeyPressed || panOnScroll);
|
||||
|
||||
const onTransformChange = (transform: Transform) =>
|
||||
(store.viewport = { x: transform[0], y: transform[1], zoom: transform[2] });
|
||||
|
||||
// We extract the initial value by destructuring
|
||||
const { viewport: initialViewport } = store;
|
||||
|
||||
onMount(() => {
|
||||
store.viewportInitialized = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -66,7 +58,10 @@
|
||||
translateExtent: store.translateExtent,
|
||||
lib: 'svelte',
|
||||
paneClickDistance,
|
||||
onTransformChange
|
||||
onTransformChange: (transform: Transform) => {
|
||||
performance.mark('svelte-flow-zoom-transform-change');
|
||||
store.viewport = { x: transform[0], y: transform[1], zoom: transform[2] };
|
||||
}
|
||||
}}
|
||||
>
|
||||
{@render children()}
|
||||
|
||||
@@ -107,7 +107,8 @@ export {
|
||||
type ResizeParams,
|
||||
type ResizeParamsWithDirection,
|
||||
type ResizeDragEvent,
|
||||
type IsValidConnection
|
||||
type IsValidConnection,
|
||||
type NodeConnection
|
||||
} from '@xyflow/system';
|
||||
|
||||
// system utils
|
||||
|
||||
@@ -63,7 +63,10 @@
|
||||
});
|
||||
let boundingRect = $derived(
|
||||
store.nodeLookup.size > 0
|
||||
? getBoundsOfRects(getInternalNodesBounds(store.nodeLookup), viewBB)
|
||||
? getBoundsOfRects(
|
||||
getInternalNodesBounds(store.nodeLookup, { filter: (n) => !n.hidden }),
|
||||
viewBB
|
||||
)
|
||||
: viewBB
|
||||
);
|
||||
let scaledWidth = $derived(boundingRect.width / width);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
fitView as fitViewSystem,
|
||||
panBy as panBySystem,
|
||||
updateNodeInternals as updateNodeInternalsSystem,
|
||||
addEdge as addEdgeUtil,
|
||||
@@ -13,9 +12,7 @@ import {
|
||||
type CoordinateExtent,
|
||||
type UpdateConnection,
|
||||
type ConnectionState,
|
||||
getFitViewNodes,
|
||||
updateAbsolutePositions,
|
||||
getDimensions
|
||||
updateAbsolutePositions
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
|
||||
@@ -72,14 +69,8 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
||||
nodeExtent: store.nodeExtent
|
||||
});
|
||||
|
||||
if (!store.fitViewOnInitDone && store.fitViewOnInit) {
|
||||
const fitViewOnInitDone = fitViewSync({
|
||||
...store.fitViewOptions,
|
||||
nodes: store.fitViewOptions?.nodes
|
||||
});
|
||||
if (fitViewOnInitDone) {
|
||||
store.fitViewOnInitDone = fitViewOnInitDone;
|
||||
}
|
||||
if (store.fitViewQueued) {
|
||||
store.resolveFitView();
|
||||
}
|
||||
|
||||
const newNodes = new Map<string, Node>();
|
||||
@@ -120,52 +111,41 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
||||
}
|
||||
|
||||
function fitView(options?: FitViewOptions) {
|
||||
const panZoom = store.panZoom;
|
||||
const domNode = store.domNode;
|
||||
// const panZoom = store.panZoom;
|
||||
// const domNode = store.domNode;
|
||||
|
||||
if (!panZoom || !domNode) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
// if (!panZoom || !domNode) {
|
||||
// return Promise.resolve(false);
|
||||
// }
|
||||
|
||||
const { width, height } = getDimensions(domNode);
|
||||
// const { width, height } = getDimensions(domNode);
|
||||
|
||||
const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
|
||||
// const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
|
||||
|
||||
return fitViewSystem(
|
||||
{
|
||||
nodes: fitViewNodes,
|
||||
width,
|
||||
height,
|
||||
minZoom: store.minZoom,
|
||||
maxZoom: store.maxZoom,
|
||||
panZoom
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
// return fitViewSystem(
|
||||
// {
|
||||
// nodes: fitViewNodes,
|
||||
// width,
|
||||
// height,
|
||||
// minZoom: store.minZoom,
|
||||
// maxZoom: store.maxZoom,
|
||||
// panZoom
|
||||
// },
|
||||
// options
|
||||
// );e3
|
||||
// We either create a new Promise or reuse the existing one
|
||||
// Even if fitView is called multiple times in a row, we only end up with a single Promise
|
||||
const fitViewResolver = store.fitViewResolver ?? Promise.withResolvers<boolean>();
|
||||
|
||||
function fitViewSync(options?: FitViewOptions) {
|
||||
const panZoom = store.panZoom;
|
||||
// We schedule a fitView by setting fitViewQueued and triggering a setNodes
|
||||
store.fitViewQueued = true;
|
||||
store.fitViewOptions = options;
|
||||
store.fitViewResolver = fitViewResolver;
|
||||
|
||||
if (!panZoom) {
|
||||
return false;
|
||||
}
|
||||
// We need to update the nodes so that adoptUserNodes is triggered
|
||||
store.nodes = [...store.nodes];
|
||||
|
||||
const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
|
||||
|
||||
fitViewSystem(
|
||||
{
|
||||
nodes: fitViewNodes,
|
||||
width: store.width,
|
||||
height: store.height,
|
||||
minZoom: store.minZoom,
|
||||
maxZoom: store.maxZoom,
|
||||
panZoom
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
return fitViewNodes.size > 0;
|
||||
return fitViewResolver.promise;
|
||||
}
|
||||
|
||||
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
|
||||
@@ -350,10 +330,11 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
||||
}
|
||||
|
||||
function reset() {
|
||||
store.fitViewOnInitDone = false;
|
||||
store.selectionRect = null;
|
||||
store.selectionRectMode = null;
|
||||
|
||||
store.resetStoreValues();
|
||||
|
||||
unselectNodesAndEdges();
|
||||
cancelConnection();
|
||||
}
|
||||
@@ -366,7 +347,7 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
|
||||
updateNodeInternals,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitView: (options?: FitViewOptions) => fitView(options),
|
||||
fitView,
|
||||
setMinZoom,
|
||||
setMaxZoom,
|
||||
setTranslateExtent,
|
||||
|
||||
@@ -27,7 +27,8 @@ import {
|
||||
type ParentLookup,
|
||||
pointToRendererPoint,
|
||||
type ColorModeClass,
|
||||
type Transform
|
||||
type Transform,
|
||||
fitViewport
|
||||
} from '@xyflow/system';
|
||||
|
||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||
@@ -51,7 +52,6 @@ import type {
|
||||
OnBeforeDelete,
|
||||
IsValidConnection,
|
||||
Edge,
|
||||
Node,
|
||||
EdgeLayouted,
|
||||
InternalNode
|
||||
} from '$lib/types';
|
||||
@@ -78,14 +78,54 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
// We use a class here, because Svelte adds getters & setter for us.
|
||||
// Inline classes have some performance implications but we just call it once (max twice).
|
||||
class SvelteFlowStore {
|
||||
_nodes: Node[] = $derived.by(() => {
|
||||
adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, {
|
||||
flowId: string = $derived(signals.props.id ?? '1');
|
||||
domNode = $state<HTMLDivElement | null>(null);
|
||||
panZoom: PanZoomInstance | null = $state(null);
|
||||
width = $state<number>(signals.width ?? 0);
|
||||
height = $state<number>(signals.height ?? 0);
|
||||
|
||||
nodesInitialized: boolean = $derived.by(() => {
|
||||
const nodesInitialized = adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, {
|
||||
nodeExtent: this.nodeExtent,
|
||||
nodeOrigin: this.nodeOrigin,
|
||||
elevateNodesOnSelect: signals.props.elevateNodesOnSelect ?? true,
|
||||
checkEquality: true
|
||||
});
|
||||
return signals.nodes;
|
||||
|
||||
if (this.fitViewQueued && nodesInitialized) {
|
||||
if (this.fitViewOptions?.duration) {
|
||||
this.resolveFitView();
|
||||
} else {
|
||||
/**
|
||||
* When no duration is set, viewport is set immediately which prevents an update
|
||||
* I do not understand why, however we are setting state in a derived which is a no-go
|
||||
*/
|
||||
queueMicrotask(() => {
|
||||
this.resolveFitView();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return nodesInitialized;
|
||||
});
|
||||
edgesInitialized: boolean = $state(false);
|
||||
viewportInitialized: boolean = $derived(this.panZoom !== null);
|
||||
|
||||
// TODO: Figure out initialized
|
||||
_initialNodesLength: number = signals.nodes?.length ?? 0;
|
||||
_initialEdgesLength: number = signals.edges?.length ?? 0;
|
||||
initialized: boolean = $derived.by(() => {
|
||||
let initialized = false;
|
||||
// if it hasn't been initialised check if it's now
|
||||
if (this._initialNodesLength === 0) {
|
||||
initialized = this.viewportInitialized;
|
||||
} else if (this._initialEdgesLength === 0) {
|
||||
initialized = this.viewportInitialized && this.nodesInitialized;
|
||||
} else {
|
||||
initialized = this.viewportInitialized && this.nodesInitialized && this.edgesInitialized;
|
||||
}
|
||||
|
||||
return initialized;
|
||||
});
|
||||
|
||||
_edges: Edge[] = $derived.by(() => {
|
||||
@@ -94,7 +134,9 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
});
|
||||
|
||||
get nodes() {
|
||||
return this._nodes;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
this.nodesInitialized;
|
||||
return signals.nodes;
|
||||
}
|
||||
set nodes(nodes) {
|
||||
signals.nodes = nodes;
|
||||
@@ -116,7 +158,7 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
const {
|
||||
// We need to access this._nodes to trigger on changes
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_nodes,
|
||||
nodes,
|
||||
_edges: edges,
|
||||
_prevVisibleEdges: previousEdges,
|
||||
nodeLookup,
|
||||
@@ -174,11 +216,6 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
signals.elementsSelectable = value;
|
||||
}
|
||||
|
||||
domNode = $state<HTMLDivElement | null>(null);
|
||||
width = $state<number>(signals.width ?? 0);
|
||||
height = $state<number>(signals.height ?? 0);
|
||||
|
||||
flowId: string = $derived(signals.props.id ?? '1');
|
||||
minZoom: number = $derived(signals.props.minZoom ?? 0.5);
|
||||
maxZoom: number = $derived(signals.props.maxZoom ?? 2);
|
||||
|
||||
@@ -192,11 +229,10 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
autoPanOnNodeDrag: boolean = $derived(signals.props.autoPanOnNodeDrag ?? true);
|
||||
autoPanOnConnect: boolean = $derived(signals.props.autoPanOnConnect ?? true);
|
||||
|
||||
fitViewOnInitDone: boolean = $state(false);
|
||||
fitViewOnInit: boolean = $derived(signals.props.fitView ?? false);
|
||||
fitViewOptions: FitViewOptions | undefined = $derived(signals.props.fitViewOptions);
|
||||
fitViewQueued: boolean = signals.props.fitView ?? false;
|
||||
fitViewOptions: FitViewOptions | undefined = signals.props.fitViewOptions;
|
||||
fitViewResolver: PromiseWithResolvers<boolean> | null = null;
|
||||
|
||||
panZoom: PanZoomInstance | null = $state(null);
|
||||
snapGrid: SnapGrid | null = $derived(signals.props.snapGrid ?? null);
|
||||
|
||||
dragging: boolean = $state(false);
|
||||
@@ -215,15 +251,16 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
|
||||
// _viewport is the internal viewport.
|
||||
// when binding to viewport, we operate on signals.viewport instead
|
||||
initial: boolean = true;
|
||||
_viewport: Viewport = $state(signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 });
|
||||
get viewport() {
|
||||
return signals.viewport ?? this._viewport;
|
||||
}
|
||||
set viewport(viewport: Viewport) {
|
||||
set viewport(newViewport: Viewport) {
|
||||
if (signals.viewport) {
|
||||
signals.viewport = viewport;
|
||||
signals.viewport = newViewport;
|
||||
}
|
||||
this._viewport = viewport;
|
||||
this._viewport = newViewport;
|
||||
}
|
||||
|
||||
// _connection is viewport independent and originating from XYHandle
|
||||
@@ -271,25 +308,32 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
onconnectend?: OnConnectEnd = $derived(signals.props.onconnectend);
|
||||
onbeforedelete?: OnBeforeDelete = $derived(signals.props.onbeforedelete);
|
||||
|
||||
nodesInitialized: boolean = $state(false);
|
||||
edgesInitialized: boolean = $state(false);
|
||||
viewportInitialized: boolean = $state(false);
|
||||
|
||||
_initialNodesLength: number = signals.nodes?.length ?? 0;
|
||||
_initialEdgesLength: number = signals.edges?.length ?? 0;
|
||||
initialized: boolean = $derived.by(() => {
|
||||
let initialized = false;
|
||||
// if it hasn't been initialised check if it's now
|
||||
if (this._initialNodesLength === 0) {
|
||||
initialized = this.viewportInitialized;
|
||||
} else if (this._initialEdgesLength === 0) {
|
||||
initialized = this.viewportInitialized && this.nodesInitialized;
|
||||
} else {
|
||||
initialized = this.viewportInitialized && this.nodesInitialized && this.edgesInitialized;
|
||||
resolveFitView = async () => {
|
||||
if (!this.panZoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
return initialized;
|
||||
});
|
||||
await fitViewport(
|
||||
{
|
||||
nodes: this.nodeLookup,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
panZoom: this.panZoom,
|
||||
minZoom: this.minZoom,
|
||||
maxZoom: this.maxZoom
|
||||
},
|
||||
this.fitViewOptions
|
||||
);
|
||||
|
||||
this.fitViewResolver?.resolve(true);
|
||||
/**
|
||||
* wait for the fitViewport to resolve before deleting the resolver,
|
||||
* we want to reuse the old resolver if the user calls fitView again in the mean time
|
||||
*/
|
||||
this.fitViewQueued = false;
|
||||
this.fitViewOptions = undefined;
|
||||
this.fitViewResolver = null;
|
||||
};
|
||||
|
||||
_prefersDark = new MediaQuery(
|
||||
'(prefers-color-scheme: dark)',
|
||||
@@ -328,7 +372,6 @@ export const getInitialStore = (signals: StoreSignals) => {
|
||||
|
||||
// Only way to check if an object is a proxy
|
||||
// is to see if is failes to perform a structured clone
|
||||
// TODO: is $state.raw really nessessary?
|
||||
function warnIfDeeplyReactive(array: unknown[] | undefined, name: string) {
|
||||
try {
|
||||
if (array && array.length > 0) {
|
||||
|
||||
@@ -12,7 +12,9 @@ import type { Node } from '$lib/types';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
|
||||
/**
|
||||
* The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component.
|
||||
* An `Edge` is the complete description with everything Svelte Flow needs to know in order to
|
||||
* render it.
|
||||
* @public
|
||||
*/
|
||||
export type Edge<
|
||||
EdgeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isNodeBase, isEdgeBase } from '@xyflow/system';
|
||||
import type { Edge, Node } from '$lib/types';
|
||||
|
||||
/**
|
||||
* Test whether an object is useable as a Node
|
||||
* Test whether an object is usable as a Node
|
||||
* @public
|
||||
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
|
||||
* @param element - The element to test
|
||||
@@ -13,7 +13,7 @@ export const isNode = <NodeType extends Node = Node>(element: unknown): element
|
||||
isNodeBase<NodeType>(element);
|
||||
|
||||
/**
|
||||
* Test whether an object is useable as an Edge
|
||||
* Test whether an object is usable as an Edge
|
||||
* @public
|
||||
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
|
||||
* @param element - The element to test
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
# @xyflow/system
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5156](https://github.com/xyflow/xyflow/pull/5156) [`02a3b746`](https://github.com/xyflow/xyflow/commit/02a3b74645799a3f0ce670b69365fa86ecb0616e) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type GetSmoothStepPathParams` and `getSmoothStepPath` function
|
||||
|
||||
- [#5168](https://github.com/xyflow/xyflow/pull/5168) [`cbe305e1`](https://github.com/xyflow/xyflow/commit/cbe305e15a5c5d3b92583e0ec12364b2509f49bd) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `getOutgoers`, `getIncomers` and `type GetNodesBoundsParams`
|
||||
|
||||
- [#5169](https://github.com/xyflow/xyflow/pull/5169) [`1f671bd4`](https://github.com/xyflow/xyflow/commit/1f671bd48f06230da841fdd1d7a312413ef16d03) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type ReconnectEdgeOptions` and `getViewportForBounds`
|
||||
|
||||
- [#5155](https://github.com/xyflow/xyflow/pull/5155) [`aaebc462`](https://github.com/xyflow/xyflow/commit/aaebc462951ded8e91374c3e084d77af5ed7380a) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type GetStraightPathParams` and `getStraightPath` function
|
||||
|
||||
- [#5157](https://github.com/xyflow/xyflow/pull/5157) [`6ec942fc`](https://github.com/xyflow/xyflow/commit/6ec942fc6501f81009c278cc995764bef3e8d03b) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `type GetBezierPathParams` and `getBezierPath` function
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#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
|
||||
|
||||
- [#5142](https://github.com/xyflow/xyflow/pull/5142) [`24a1bc89`](https://github.com/xyflow/xyflow/commit/24a1bc89348817ed9b5c87f74bf2519c705143be) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `HandleProps`, `NodeBase` and `InternalNodeBase`
|
||||
|
||||
- [#5136](https://github.com/xyflow/xyflow/pull/5136) [`36657cd6`](https://github.com/xyflow/xyflow/commit/36657cd66322c911e87eb37275c584a80025adfe) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `EdgeBase`
|
||||
|
||||
- [#5139](https://github.com/xyflow/xyflow/pull/5139) [`89de9ca8`](https://github.com/xyflow/xyflow/commit/89de9ca83fbf9263a687a0f5f915efb2beb31654) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Use `rgba` for colors with opacity instead of `rgb` for `MiniMap` mask color
|
||||
|
||||
- [#5148](https://github.com/xyflow/xyflow/pull/5148) [`2ac6e155`](https://github.com/xyflow/xyflow/commit/2ac6e155e35256ca436281df16344366e7d05761) Thanks [@moklick](https://github.com/moklick)! - Prevent browser zoom for pinch zoom gestures on nowheel elements
|
||||
|
||||
- [#5137](https://github.com/xyflow/xyflow/pull/5137) [`f0f378e5`](https://github.com/xyflow/xyflow/commit/f0f378e5b6918c2c30d9dc1e32587063cb942d4e) Thanks [@dimaMachina](https://github.com/dimaMachina)! - Improve TSDoc comments for `Connection` and `ConnectionInProgress`
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5118](https://github.com/xyflow/xyflow/pull/5118) [`5d15b01b`](https://github.com/xyflow/xyflow/commit/5d15b01ba8cb349d6397a6ed8162848b4dfec293) Thanks [@moklick](https://github.com/moklick)! - Do not swallow key events when a button is focused
|
||||
|
||||
- [#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
|
||||
|
||||
- [#5091](https://github.com/xyflow/xyflow/pull/5091) [`a79f30b3`](https://github.com/xyflow/xyflow/commit/a79f30b3dd7c8ff6400c8d22214b2c2282e5bac1) Thanks [@moklick](https://github.com/moklick)! - Add center-left and center-right as a panel position
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### 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
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### 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
|
||||
|
||||
- [#4990](https://github.com/xyflow/xyflow/pull/4990) [`4947029c`](https://github.com/xyflow/xyflow/commit/4947029cd6cda0f695e1fb4815e4030adb232234) Thanks [@damianstasik](https://github.com/damianstasik)! - Make it possible to stop autoPanOnDrag by setting it 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
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/system",
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.55",
|
||||
"description": "xyflow core system that powers React Flow and Svelte Flow.",
|
||||
"keywords": [
|
||||
"node-based UI",
|
||||
@@ -63,7 +63,7 @@
|
||||
"@xyflow/eslint-config": "workspace:*",
|
||||
"@xyflow/rollup-config": "workspace:*",
|
||||
"@xyflow/tsconfig": "workspace:*",
|
||||
"typescript": "5.1.3"
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"rollup": {
|
||||
"globals": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user