chore: fix conflicts

This commit is contained in:
chang_su
2022-10-25 14:56:37 +08:00
112 changed files with 1497 additions and 693 deletions
+37 -12
View File
@@ -10,7 +10,7 @@
A highly customizable React component for building interactive graphs and node-based editors.
[🚀 Getting Started](https://reactflow.dev/docs/getting-started/installation) | [📖 Documentation](https://reactflow.dev/docs/api/react-flow-props) | [📺 Examples](https://reactflow.dev/docs/examples/overview) | [☎️ Discord](https://discord.gg/RVmnytFmGW) | [💎 React Flow Pro](https://pro.reactflow.dev/pricing)
[🚀 Getting Started](https://reactflow.dev/docs/quickstart/) | [📖 Documentation](https://reactflow.dev/docs/api/react-flow-props) | [📺 Examples](https://reactflow.dev/docs/examples/overview) | [☎️ Discord](https://discord.gg/RVmnytFmGW) | [💎 React Flow Pro](https://pro.reactflow.dev)
</div>
@@ -25,11 +25,13 @@ A highly customizable React component for building interactive graphs and node-b
- **Plugin Components:** [Background](https://reactflow.dev/docs/api/plugin-components/background), [MiniMap](https://reactflow.dev/docs/api/plugin-components/minimap) and [Controls](https://reactflow.dev/docs/api/plugin-components/controls)
- **Reliable**: Written in [Typescript](https://www.typescriptlang.org/) and tested with [cypress](https://www.cypress.io/)
## Commercial Usage / Attribution
## Commercial Usage
React Flow includes a small attribution that links to the React Flow website. **We expect companies who are using React Flow commercially to subscribe to [React Flow Pro](https://pro.reactflow.dev/pricing) if they want to remove the attribution.** By subscribing you get access to other exclusive services like advanced examples, individual support or prioritized bug reports. In non-commercial applications you may hide the attribution without subscribing but are welcome to [sponsor us on Github](https://github.com/sponsors/wbkd).
**Are you using React Flow for a personal project?** Great! No sponsorship needed, you can support us by reporting any bugs you find, sending us screenshots of your projects, and starring us on Github 🌟
You can find more information in our [React Flow Pro FAQs](https://pro.reactflow.dev/faq).
**Are you using React Flow at your organization and making money from it?** Awesome! We rely on your support to keep React Flow developed and maintained under an MIT License, just how we like it. You can do that on the [React Flow Pro website](https://pro.reactflow.dev) or through [Github Sponsors](https://github.com/sponsors/wbkd).
You can find more information in our [React Flow Pro FAQs](https://pro.reactflow.dev/info).
## Installation
@@ -39,14 +41,36 @@ The easiest way to get the latest version of React Flow is to install it via npm
npm install reactflow
```
## Quick Start
## Quickstart
This is only a very basic usage example of React Flow. To see everything that is possible with the library, please refer to the [website](https://reactflow.dev) for [guides](https://reactflow.dev/docs/guides/custom-nodes), [examples](https://reactflow.dev/docs/examples/overview) and [API reference](https://reactflow.dev/docs/api/react-flow-props).
This is only a very basic usage example of React Flow. To see everything that is possible with the library, please refer to the [website](https://reactflow.dev) for [guides](https://reactflow.dev/docs/guides/custom-nodes), [examples](https://reactflow.dev/docs/examples/overview) and the full [API reference](https://reactflow.dev/docs/api/react-flow-props).
```jsx
import ReactFlow, { MiniMap, Controls } from 'reactflow';
import { useCallback } from 'react';
import ReactFlow, {
MiniMap,
Controls,
Background,
useNodesState,
useEdgesState,
addEdge,
} from 'reactflow';
import 'reactflow/dist/style.css';
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },
{ id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
return (
<ReactFlow
nodes={nodes}
@@ -57,6 +81,7 @@ function Flow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) {
>
<MiniMap />
<Controls />
<Background />
</ReactFlow>
);
}
@@ -78,12 +103,12 @@ pnpm test
## Maintainers
React Flow is developed and maintained by [webkid](https://webkid.io), a web development agency with focus on data driven applications from Berlin. If you need help or want to talk to us about a collaboration, feel free to contact us:
React Flow is the full-time project of Moritz and Christopher of [webkid](https://webkid.io/), based in Berlin. If you need help or want to talk to us about a collaboration, reach out through our [contact form](https://pro.reactflow.dev/contact) or by joining the [React Flow Discord Server](https://discord.gg/Bqt6xrs).
- Moritz Klack • [Twitter](https://twitter.com/moklick)[Github](https://github.com/moklick)
- Christopher Möller • [Twitter](https://twitter.com/chrtze)[Github](https://github.com/chrtze)
- Moritz Klack • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick)
- Christopher Möller • [Twitter](https://twitter.com/chrtze) • [Github](https://github.com/chrtze)
You can also use our [contact form](https://pro.reactflow.dev/contact) or join the [React Flow Discord Server](https://discord.gg/Bqt6xrs).
Any support you provide goes directly towards the development and maintenance of React Flow, allowing us to continue to operate as an independent company, working on what we think is best for React Flow as an open-source library.
## Community Packages
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>React Flow Examples</title>
</head>
<body>
<div id="root"></div>
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@reactflow/examples",
"private": true,
"version": "0.0.0",
"version": "0.0.2",
"type": "module",
"scripts": {
"dev": "vite --port 3000 --open",
@@ -13,6 +13,7 @@
"test-e2e": "start-server-and-test 'pnpm serve' http-get://localhost:3000 'pnpm test-e2e-cypress'"
},
"dependencies": {
"classcat": "^5.0.3",
"dagre": "^0.8.5",
"localforage": "^1.10.0",
"react": "^18.2.0",
+64 -6
View File
@@ -1,4 +1,4 @@
import { ChangeEvent } from 'react';
import { useEffect, useState } from 'react';
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import Basic from '../examples/Basic';
@@ -35,160 +35,218 @@ import UseReactFlow from '../examples/UseReactFlow';
import Validation from '../examples/Validation';
import UseKeyPress from '../examples/UseKeyPress';
import EdgeRouting from '../examples/EdgeRouting';
import CancelConnection from '../examples/CancelConnection';
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
const routes = [
interface IRoute {
name: string;
path: string;
component: React.ComponentType;
}
const routes: IRoute[] = [
{
name: 'Basic',
path: '/',
component: Basic,
},
{
name: 'Backgrounds',
path: '/backgrounds',
component: Backgrounds,
},
{
name: 'Cancel Connection',
path: '/cancel-connection',
component: CancelConnection,
},
{
name: 'Controlled/Uncontrolled',
path: '/controlled-uncontrolled',
component: ControlledUncontrolled,
},
{
name: 'Custom Connection Line',
path: '/custom-connectionline',
component: CustomConnectionLine,
},
{
name: 'Custom Node',
path: '/custom-node',
component: CustomNode,
},
{
name: 'Default Nodes',
path: '/default-nodes',
component: DefaultNodes,
},
{
name: 'Drag Handle',
path: '/draghandle',
component: DragHandle,
},
{
name: 'Drag and Drop',
path: '/dragndrop',
component: DragNDrop,
},
{
name: 'Edges',
path: '/edges',
component: Edges,
},
{
name: 'Edge Types',
path: '/edge-types',
component: EdgeTypes,
},
{
name: 'Edge Routing',
path: '/edge-routing',
component: EdgeRouting,
},
{
name: 'Empty',
path: '/empty',
component: Empty,
},
{
name: 'Floating Edges',
path: '/floating-edges',
component: FloatingEdges,
},
{
name: 'Hidden',
path: '/hidden',
component: Hidden,
},
{
name: 'Interaction',
path: '/interaction',
component: Interaction,
},
{
name: 'Layouting',
path: '/layouting',
component: Layouting,
},
{
name: 'Multi Flows',
path: '/multiflows',
component: MultiFlows,
},
{
name: 'Nested Nodes',
path: '/nested-nodes',
component: NestedNodes,
},
{
name: 'Node Type Change',
path: '/nodetype-change',
component: NodeTypeChange,
},
{
name: 'nodeTypes Object Change',
path: '/nodetypesobject-change',
component: NodeTypesObjectChange,
},
{
name: 'Overview',
path: '/overview',
component: Overview,
},
{
name: 'Provider',
path: '/provider',
component: Provider,
},
{
name: 'Save/Restore',
path: '/save-restore',
component: SaveRestore,
},
{
name: 'Stress',
path: '/stress',
component: Stress,
},
{
name: 'Subflow',
path: '/subflow',
component: Subflow,
},
{
name: 'Switch Flow',
path: '/switch',
component: SwitchFlow,
},
{
name: 'Touch Device',
path: '/touch-device',
component: TouchDevice,
},
{
name: 'Undirectional',
path: '/undirectional',
component: Undirectional,
},
{
name: 'Updatable Edge',
path: '/updatable-edge',
component: UpdatableEdge,
},
{
name: 'Update Node',
path: '/update-node',
component: UpdateNode,
},
{
name: 'useOnSelectionChange',
path: '/use-on-selection-change',
component: UseOnSelectionChange,
},
{
name: 'useReactFlow',
path: '/usereactflow',
component: UseReactFlow,
},
{
name: 'useUpdateNodeInternals',
path: '/useupdatenodeinternals',
component: UseUpdateNodeInternals,
},
{
name: 'Validation',
path: '/validation',
component: Validation,
},
{
name: 'useKeyPress',
path: '/use-key-press',
component: UseKeyPress,
},
];
const Header = () => {
const navigate = useNavigate();
const location = useLocation();
const onChange = (event: ChangeEvent<HTMLSelectElement>) => navigate(event.target.value);
const navigate = useNavigate();
const [currentPath, setCurrentPath] = useState(location.pathname);
useEffect(() => {
const name = routes.find((route) => route.path === currentPath)?.name;
document.title = `React Flow Examples${name ? ' - ' + name : ''}`;
navigate(currentPath);
}, [currentPath]);
return (
<header>
<a className="logo" href="https://github.com/wbkd/react-flow">
React Flow Dev
</a>
<select defaultValue={location.pathname} onChange={onChange}>
<select value={currentPath} onChange={(event) => setCurrentPath(event.target.value)}>
{routes.map((route) => (
<option value={route.path} key={route.path}>
{route.path === '/' ? 'basic' : route.path.substring(1, route.path.length)}
{route.name}
</option>
))}
</select>
@@ -92,7 +92,7 @@ const BasicFlow = () => {
defaultEdgeOptions={defaultEdgeOptions}
selectNodesOnDrag={false}
>
<Background variant={BackgroundVariant.Lines} />
<Background variant={BackgroundVariant.Dots} />
<MiniMap />
<Controls />
@@ -0,0 +1,31 @@
.Timer {
position: absolute;
bottom: 0;
transition-duration: 0.3s;
left: 50%;
transform: translate(-50%, 100%);
font-size: 1.5rem;
z-index: 999;
background: linear-gradient(#fff, #fff, #f5f5f5);
padding: 0.8rem 1.5rem;
border-radius: 5px;
border: 1px solid #ccc;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
}
.Timer.show {
transform: translate(-50%, 0%) translateY(-15px);
}
.progress {
display: none;
position: absolute;
bottom: 0;
left: 0;
height: 5px;
background: linear-gradient(to right, #42df96, #16dfed);
}
.Timer.show .progress {
display: block;
}
@@ -0,0 +1,28 @@
import cc from 'classcat';
import styles from './Timer.module.css';
interface Props {
duration: number;
remaining: number;
show: boolean;
}
export default function Timer({
duration,
remaining,
show,
}: Props) {
const percentage = 100 - (remaining / duration) * 100;
return (
<div className={cc({
[styles.Timer]: true,
[styles.show]: show,
})}>
<div className={styles.progress} style={{
width: `${percentage}%`,
}} />
Connection will be canceled in {remaining} seconds
</div>
)
}
@@ -0,0 +1,34 @@
import { Edge, Node } from 'reactflow';
export const initialNodes: Node[] = [
{
id: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
className: 'light',
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
className: 'light',
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 100 },
className: 'light',
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 400, y: 200 },
className: 'light',
},
];
export const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
];
@@ -0,0 +1,35 @@
import { useRef, useState } from 'react';
const useCountdown = (callback: () => void) => {
const interval = useRef<NodeJS.Timer>();
const [remaining, setRemaining] = useState(0);
const start = (duration: number) => {
setRemaining(duration);
interval.current = setInterval(() => {
setRemaining((prev) => {
if (prev === 1) {
clearInterval(interval.current);
callback();
}
return prev - 1;
});
}, 1000);
};
const stop = () => {
clearInterval(interval.current);
setRemaining(0);
};
return {
start,
remaining,
stop,
counting: remaining > 0,
};
};
export default useCountdown;
@@ -0,0 +1,62 @@
import ReactFlow, {
Background,
MiniMap,
addEdge,
ReactFlowProvider,
Connection,
Edge,
useNodesState,
useEdgesState,
OnConnectStart,
OnConnectEnd,
useStore,
} from 'reactflow';
import useCountdown from './hooks/useCountdown';
import { initialEdges, initialNodes } from './data';
import Timer from './Timer';
const CANCEL_AFTER = 5; // seconds
const CancelConnection = () => {
const [nodes, _, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const cancelConnection = useStore(state => state.cancelConnection)
// Cancels connection after 5 seconds
const countdown = useCountdown(() => cancelConnection());
const onConnectStart: OnConnectStart = () => countdown.start(CANCEL_AFTER);
const onConnectEnd: OnConnectEnd = () => countdown.stop();
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
return (
<>
<Timer
duration={CANCEL_AFTER}
show={countdown.counting}
remaining={countdown.remaining}
/>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnectStart={onConnectStart}
onConnectEnd={onConnectEnd}
onConnect={onConnect}
fitView
maxZoom={2}
>
<Background />
<MiniMap />
</ReactFlow>
</>
);
}
export default () => (
<ReactFlowProvider>
<CancelConnection />
</ReactFlowProvider>
);
@@ -1,4 +1,3 @@
import React from 'react';
import ReactFlow, {
useReactFlow,
Node,
@@ -1,4 +1,3 @@
import React from 'react';
import ReactFlow, { useReactFlow, Node, Edge, ReactFlowProvider, Background, BackgroundVariant } from 'reactflow';
const defaultNodes: Node[] = [
@@ -1,4 +1,3 @@
import React from 'react';
import ReactFlow, { Node, Edge, Position, MarkerType } from 'reactflow';
const nodes: Node[] = [
@@ -107,7 +106,7 @@ const edges: Edge[] = [
const defaultEdgeOptions = {
label: 'Edge Label',
type: 'default',
type: 'smoothstep',
markerEnd: {
type: MarkerType.ArrowClosed,
},
@@ -1,4 +1,3 @@
import React from 'react';
import { useStore, useStoreApi } from 'reactflow';
import styles from './provider.module.css';
@@ -1,4 +1,3 @@
import React from 'react';
import { useKeyPress } from 'reactflow';
const UseKeyPressComponent = () => {
@@ -0,0 +1,36 @@
import React, { useState, memo, FC, useMemo, CSSProperties } from 'react';
import { Handle, Position, NodeProps, useUpdateNodeInternals } from 'reactflow';
const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' };
const CustomNode: FC<NodeProps> = ({ id }) => {
const [handleCount, setHandleCount] = useState(1);
const updateNodeInternals = useUpdateNodeInternals();
const handles = useMemo(
() =>
Array.from({ length: handleCount }, (x, i) => {
const handleId = `handle-${i}`;
return <Handle key={handleId} type="source" position={Position.Right} id={handleId} style={{ top: 10 * i }} />;
}),
[handleCount]
);
return (
<div style={nodeStyles}>
<Handle type="target" position={Position.Left} />
<div>output handle count: {handleCount}</div>
<button
onClick={() => {
setHandleCount((c) => c + 1);
updateNodeInternals(id);
}}
>
add handle
</button>
{handles}
</div>
);
};
export default memo(CustomNode);
@@ -0,0 +1,58 @@
import { useCallback } from 'react';
import ReactFlow, {
addEdge,
ReactFlowProvider,
Node,
Connection,
Edge,
useNodesState,
useEdgesState,
useOnSelectionChange,
OnSelectionChangeParams,
} from 'reactflow';
const initialNodes: Node[] = [
{
id: '1',
type: 'default',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
},
];
const SelectionLogger = () => {
const onChange = useCallback(({ nodes, edges }: OnSelectionChangeParams) => {
console.log(nodes, edges);
}, []);
useOnSelectionChange({
onChange,
});
return null;
};
const Flow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
/>
);
};
const WrappedFlow = () => (
<ReactFlowProvider>
<Flow />
<SelectionLogger />
</ReactFlowProvider>
);
export default WrappedFlow;
+2 -2
View File
@@ -16,8 +16,8 @@
"clean": "pnpm -r --parallel exec rimraf dist .turbo"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.6",
"@changesets/cli": "^2.24.3",
"@changesets/changelog-github": "^0.4.7",
"@changesets/cli": "^2.25.0",
"@preconstruct/cli": "^2.2.1",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
+60
View File
@@ -0,0 +1,60 @@
# @reactflow/background
## 11.0.3
### Patch Changes
- cleanup types
- cleanup rf id handling
- Updated dependencies:
- @reactflow/core@11.1.2
## 11.0.2
### Patch Changes
- Updated dependencies:
- @reactflow/core@11.1.1
## 11.0.1
### Patch Changes
- [`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab) Thanks [@moklick](https://github.com/moklick)! - default gap = 20
- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]:
- @reactflow/core@11.1.0
## 11.0.0
### Major Changes
- **Better [Accessibility](/docs/guides/accessibility)**
- Nodes and edges are focusable, selectable, moveable and deleteable with the keyboard.
- `aria-` default attributes for all elements and controllable via `ariaLabel` options
- Keyboard controls can be disabled with the new `disableKeyboardA11y` prop
- **Better selectable edges** via new edge option: `interactionWidth` - renders invisible edge that makes it easier to interact
- **Better routing for smoothstep and step edges**: https://twitter.com/reactflowdev/status/1567535405284614145
- **Nicer edge updating behaviour**: https://twitter.com/reactflowdev/status/1564966917517021184
- **Node origin**: The new `nodeOrigin` prop lets you control the origin of a node. Useful for layouting.
- **New background pattern**: `BackgroundVariant.Cross` variant
- **[`useOnViewportChange`](/docs/api/hooks/use-on-viewport-change) hook** - handle viewport changes within a component
- **[`useOnSelectionChange`](/docs/api/hooks/use-on-selection-change) hook** - handle selection changes within a component
- **[`useNodesInitialized`](/docs/api/hooks/use-nodes-initialized) hook** - returns true if all nodes are initialized and if there is more than one node
- **Deletable option** for Nodes and edges
- **New Event handlers**: `onPaneMouseEnter`, `onPaneMouseMove` and `onPaneMouseLeave`
- **Edge `pathOptions`** for `smoothstep` and `default` edges
- **Nicer cursor defaults**: Cursor is grabbing, while dragging a node or panning
- **Pane moveable** with middle mouse button
- **Pan over nodes** when they are not draggable (`draggable=false` or `nodesDraggable` false)
- **[`<BaseEdge />`](/docs/api/edges/base-edge) component** that makes it easier to build custom edges
- **[Separately installable packages](/docs/overview/packages/)**
- @reactflow/core
- @reactflow/background
- @reactflow/controls
- @reactflow/minimap
### Patch Changes
- Updated dependencies:
- @reactflow/core@11.0.0
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@reactflow/background",
"version": "11.0.0-next.6",
"version": "11.0.3",
"description": "Background component with different variants for React Flow",
"keywords": [
"react",
@@ -17,7 +17,9 @@
"main": "dist/umd/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"sideEffects": false,
"sideEffects": [
"*.css"
],
"scripts": {
"dev": "rollup --config node:@reactflow/rollup-config --watch",
"build": "rollup --config node:@reactflow/rollup-config --environment NODE_ENV:production",
+5 -5
View File
@@ -18,11 +18,11 @@ const defaultSize = {
[BackgroundVariant.Cross]: 6,
};
const selector = (s: ReactFlowState) => ({ transform: s.transform, rfId: s.rfId });
const selector = (s: ReactFlowState) => ({ transform: s.transform, patternId: `pattern-${s.rfId}` });
function Background({
variant = BackgroundVariant.Dots,
gap = 25,
gap = 20,
// only used for dots and cross
size,
// only used for lines and cross
@@ -32,7 +32,7 @@ function Background({
className,
}: BackgroundProps) {
const ref = useRef<SVGSVGElement>(null);
const { transform, rfId } = useStore(selector, shallow);
const { transform, patternId } = useStore(selector, shallow);
const patternColor = color || defaultColor[variant];
const patternSize = size || defaultSize[variant];
const isDots = variant === BackgroundVariant.Dots;
@@ -61,7 +61,7 @@ function Background({
ref={ref}
>
<pattern
id={rfId}
id={patternId}
x={transform[0] % scaledGap[0]}
y={transform[1] % scaledGap[1]}
width={scaledGap[0]}
@@ -75,7 +75,7 @@ function Background({
<LinePattern dimensions={patternDimensions} color={patternColor} lineWidth={lineWidth} />
)}
</pattern>
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${rfId})`} />
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
</svg>
);
}
+2 -2
View File
@@ -6,7 +6,7 @@ export enum BackgroundVariant {
Cross = 'cross',
}
export interface BackgroundProps {
export type BackgroundProps = {
color?: string;
className?: string;
gap?: number | [number, number];
@@ -14,4 +14,4 @@ export interface BackgroundProps {
lineWidth?: number;
variant?: BackgroundVariant;
style?: CSSProperties;
}
};
+57
View File
@@ -0,0 +1,57 @@
# @reactflow/controls
## 11.0.3
### Patch Changes
- cleanup types
- Updated dependencies:
- @reactflow/core@11.1.2
## 11.0.2
### Patch Changes
- Updated dependencies:
- @reactflow/core@11.1.1
## 11.0.1
### Patch Changes
- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]:
- @reactflow/core@11.1.0
## 11.0.0
### Major Changes
- **Better [Accessibility](/docs/guides/accessibility)**
- Nodes and edges are focusable, selectable, moveable and deleteable with the keyboard.
- `aria-` default attributes for all elements and controllable via `ariaLabel` options
- Keyboard controls can be disabled with the new `disableKeyboardA11y` prop
- **Better selectable edges** via new edge option: `interactionWidth` - renders invisible edge that makes it easier to interact
- **Better routing for smoothstep and step edges**: https://twitter.com/reactflowdev/status/1567535405284614145
- **Nicer edge updating behaviour**: https://twitter.com/reactflowdev/status/1564966917517021184
- **Node origin**: The new `nodeOrigin` prop lets you control the origin of a node. Useful for layouting.
- **New background pattern**: `BackgroundVariant.Cross` variant
- **[`useOnViewportChange`](/docs/api/hooks/use-on-viewport-change) hook** - handle viewport changes within a component
- **[`useOnSelectionChange`](/docs/api/hooks/use-on-selection-change) hook** - handle selection changes within a component
- **[`useNodesInitialized`](/docs/api/hooks/use-nodes-initialized) hook** - returns true if all nodes are initialized and if there is more than one node
- **Deletable option** for Nodes and edges
- **New Event handlers**: `onPaneMouseEnter`, `onPaneMouseMove` and `onPaneMouseLeave`
- **Edge `pathOptions`** for `smoothstep` and `default` edges
- **Nicer cursor defaults**: Cursor is grabbing, while dragging a node or panning
- **Pane moveable** with middle mouse button
- **Pan over nodes** when they are not draggable (`draggable=false` or `nodesDraggable` false)
- **[`<BaseEdge />`](/docs/api/edges/base-edge) component** that makes it easier to build custom edges
- **[Separately installable packages](/docs/overview/packages/)**
- @reactflow/core
- @reactflow/background
- @reactflow/controls
- @reactflow/minimap
### Patch Changes
- Updated dependencies:
- @reactflow/core@11.0.0
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@reactflow/controls",
"version": "11.0.0-next.6",
"version": "11.0.3",
"description": "Component to control the viewport of a React Flow instance",
"keywords": [
"react",
@@ -17,7 +17,9 @@
"main": "dist/umd/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"sideEffects": false,
"sideEffects": [
"*.css"
],
"license": "MIT",
"repository": {
"type": "git",
+4 -12
View File
@@ -1,18 +1,10 @@
import { FC, PropsWithChildren } from 'react';
import type { FC, PropsWithChildren } from 'react';
import cc from 'classcat';
import { ControlButtonProps } from './types';
import type { ControlButtonProps } from './types';
const ControlButton: FC<PropsWithChildren<ControlButtonProps>> = ({
children,
className,
...rest
}) => (
<button
type="button"
className={cc(['react-flow__controls-button', className])}
{...rest}
>
const ControlButton: FC<PropsWithChildren<ControlButtonProps>> = ({ children, className, ...rest }) => (
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
{children}
</button>
);
+5 -3
View File
@@ -1,6 +1,8 @@
import { memo, FC, useEffect, useState, PropsWithChildren } from 'react';
import { memo, useEffect, useState } from 'react';
import type { FC, PropsWithChildren } from 'react';
import cc from 'classcat';
import { useStore, useStoreApi, useReactFlow, ReactFlowState, Panel } from '@reactflow/core';
import { useStore, useStoreApi, useReactFlow, Panel } from '@reactflow/core';
import type { ReactFlowState } from '@reactflow/core';
import PlusIcon from './Icons/Plus';
import MinusIcon from './Icons/Minus';
@@ -9,7 +11,7 @@ import LockIcon from './Icons/Lock';
import UnlockIcon from './Icons/Unlock';
import ControlButton from './ControlButton';
import { ControlProps } from './types';
import type { ControlProps } from './types';
const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable;
+4 -4
View File
@@ -1,7 +1,7 @@
import { ButtonHTMLAttributes, HTMLAttributes } from 'react';
import { FitViewOptions, PanelPosition } from '@reactflow/core';
import type { ButtonHTMLAttributes, HTMLAttributes } from 'react';
import type { FitViewOptions, PanelPosition } from '@reactflow/core';
export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
export type ControlProps = HTMLAttributes<HTMLDivElement> & {
showZoom?: boolean;
showFitView?: boolean;
showInteractive?: boolean;
@@ -11,6 +11,6 @@ export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
onFitView?: () => void;
onInteractiveChange?: (interactiveStatus: boolean) => void;
position?: PanelPosition;
}
};
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
+59
View File
@@ -0,0 +1,59 @@
# @reactflow/core
## 11.1.2
### Patch Changes
- make pro options acc type optional
- cleanup types
- fix rf id handling
- always render nodes when dragging=true
- don't apply animations to helper edge
## 11.1.1
### Patch Changes
- [`c44413d`](https://github.com/wbkd/react-flow/commit/c44413d816604ae2d6ad81ed227c3dfde1a7bd8a) Thanks [@moklick](https://github.com/moklick)! - chore(panel): dont break user selection above panel
- [`48c402c`](https://github.com/wbkd/react-flow/commit/48c402c4d3bd9e16dc91cd4c549324e57b6d5c57) Thanks [@moklick](https://github.com/moklick)! - refactor(aria-descriptions): render when disableKeyboardA11y is true
- [`3a1a365`](https://github.com/wbkd/react-flow/commit/3a1a365a63fc4564d9a8d96309908986fcc86f95) Thanks [@moklick](https://github.com/moklick)! - fix(useOnSelectionChange): repair hook closes #2484
- [`5d35094`](https://github.com/wbkd/react-flow/commit/5d350942d33ded626b3387206f0b0dee368efdfb) Thanks [@neo](https://github.com/neo)! - Add css files as sideEffects
## 11.1.0
### Minor Changes
- [`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab) Thanks [@moklick](https://github.com/moklick)! - New props: nodesFocusable and edgesFocusable
### Patch Changes
- [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4) Thanks [@moklick](https://github.com/moklick)! - Make nopan class name overwritable with class name option
## 11.0.0
### Major Changes
- **Better [Accessibility](/docs/guides/accessibility)**
- Nodes and edges are focusable, selectable, moveable and deleteable with the keyboard.
- `aria-` default attributes for all elements and controllable via `ariaLabel` options
- Keyboard controls can be disabled with the new `disableKeyboardA11y` prop
- **Better selectable edges** via new edge option: `interactionWidth` - renders invisible edge that makes it easier to interact
- **Better routing for smoothstep and step edges**: https://twitter.com/reactflowdev/status/1567535405284614145
- **Nicer edge updating behaviour**: https://twitter.com/reactflowdev/status/1564966917517021184
- **Node origin**: The new `nodeOrigin` prop lets you control the origin of a node. Useful for layouting.
- **New background pattern**: `BackgroundVariant.Cross` variant
- **[`useOnViewportChange`](/docs/api/hooks/use-on-viewport-change) hook** - handle viewport changes within a component
- **[`useOnSelectionChange`](/docs/api/hooks/use-on-selection-change) hook** - handle selection changes within a component
- **[`useNodesInitialized`](/docs/api/hooks/use-nodes-initialized) hook** - returns true if all nodes are initialized and if there is more than one node
- **Deletable option** for Nodes and edges
- **New Event handlers**: `onPaneMouseEnter`, `onPaneMouseMove` and `onPaneMouseLeave`
- **Edge `pathOptions`** for `smoothstep` and `default` edges
- **Nicer cursor defaults**: Cursor is grabbing, while dragging a node or panning
- **Pane moveable** with middle mouse button
- **Pan over nodes** when they are not draggable (`draggable=false` or `nodesDraggable` false)
- **[`<BaseEdge />`](/docs/api/edges/base-edge) component** that makes it easier to build custom edges
- **[Separately installable packages](/docs/overview/packages/)**
- @reactflow/core
- @reactflow/background
- @reactflow/controls
- @reactflow/minimap
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@reactflow/core",
"version": "11.0.0-next.6",
"version": "11.1.2",
"description": "Core components and util functions of React Flow.",
"keywords": [
"react",
@@ -17,7 +17,9 @@
"main": "dist/umd/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"sideEffects": false,
"sideEffects": [
"*.css"
],
"license": "MIT",
"publishConfig": {
"access": "public"
@@ -1,6 +1,6 @@
import { CSSProperties } from 'react';
import { useStore } from '../../hooks/useStore';
import { ReactFlowState } from '../../types';
import type { ReactFlowState } from '../../types';
const style: CSSProperties = { display: 'none' };
const ariaLiveStyle: CSSProperties = {
@@ -17,25 +17,32 @@ const ariaLiveStyle: CSSProperties = {
export const ARIA_NODE_DESC_KEY = 'react-flow__node-desc';
export const ARIA_EDGE_DESC_KEY = 'react-flow__edge-desc';
export const ARIA_LIVE_MESSAGE = 'react-flow__arai-live';
export const ARIA_LIVE_MESSAGE = 'react-flow__aria-live';
const selector = (s: ReactFlowState) => s.ariaLiveMessage;
function A11yDescriptions({ rfId }: { rfId: string }) {
function AriaLiveMessage({ rfId }: { rfId: string }) {
const ariaLiveMessage = useStore(selector);
return (
<div id={`${ARIA_LIVE_MESSAGE}-${rfId}`} aria-live="assertive" aria-atomic="true" style={ariaLiveStyle}>
{ariaLiveMessage}
</div>
);
}
function A11yDescriptions({ rfId, disableKeyboardA11y }: { rfId: string; disableKeyboardA11y: boolean }) {
return (
<>
<div id={`${ARIA_NODE_DESC_KEY}-${rfId}`} style={style}>
Press enter or space to select a node. You can then use the arrow keys to move the node around, press delete to
remove it and press escape to cancel.
Press enter or space to select a node.
{!disableKeyboardA11y && 'You can then use the arrow keys to move the node around.'} Press delete to remove it
and escape to cancel.{' '}
</div>
<div id={`${ARIA_EDGE_DESC_KEY}-${rfId}`} style={style}>
Press enter or space to select an edge. You can then press delete to remove it or press escape to cancel.
</div>
<div id={`${ARIA_LIVE_MESSAGE}-${rfId}`} aria-live="assertive" aria-atomic="true" style={ariaLiveStyle}>
{ariaLiveMessage}
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
</div>
{!disableKeyboardA11y && <AriaLiveMessage rfId={rfId} />}
</>
);
}
@@ -1,5 +1,5 @@
import Panel from '../Panel';
import { PanelPosition, ProOptions } from '../../types';
import type { PanelPosition, ProOptions } from '../../types';
type AttributionProps = {
proOptions?: ProOptions;
@@ -4,9 +4,10 @@ import shallow from 'zustand/shallow';
import { useStore } from '../../hooks/useStore';
import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import { ConnectionLineType, ConnectionLineComponent, HandleType, Position, ReactFlowStore } from '../../types';
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
import { internalsSymbol } from '../../utils';
import type { ConnectionLineComponent, HandleType, ReactFlowStore } from '../../types';
import { Position, ConnectionLineType } from '../../types';
type ConnectionLineProps = {
connectionNodeId: string;
@@ -1,5 +1,5 @@
import EdgeText from './EdgeText';
import { BaseEdgeProps } from '../../types';
import type { BaseEdgeProps } from '../../types';
const BaseEdge = ({
path,
@@ -26,7 +26,15 @@ const BaseEdge = ({
markerEnd={markerEnd}
markerStart={markerStart}
/>
{interactionWidth && <path d={path} fill="none" strokeOpacity={0} strokeWidth={interactionWidth} />}
{interactionWidth && (
<path
d={path}
fill="none"
strokeOpacity={0}
strokeWidth={interactionWidth}
className="react-flow__edge-interaction"
/>
)}
{label ? (
<EdgeText
x={labelX}
@@ -1,7 +1,9 @@
import { memo } from 'react';
import { BezierEdgeProps, Position } from '../../types';
import BaseEdge from './BaseEdge';
import { getBezierEdgeCenter } from './utils';
import { Position } from '../../types';
import type { BezierEdgeProps } from '../../types';
export interface GetBezierPathParams {
sourceX: number;
@@ -25,40 +27,22 @@ interface GetControlWithCurvatureParams {
function calculateControlOffset(distance: number, curvature: number): number {
if (distance >= 0) {
return 0.5 * distance;
} else {
return curvature * 25 * Math.sqrt(-distance);
}
return curvature * 25 * Math.sqrt(-distance);
}
function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] {
let ctX: number, ctY: number;
switch (pos) {
case Position.Left:
{
ctX = x1 - calculateControlOffset(x1 - x2, c);
ctY = y1;
}
break;
return [x1 - calculateControlOffset(x1 - x2, c), y1];
case Position.Right:
{
ctX = x1 + calculateControlOffset(x2 - x1, c);
ctY = y1;
}
break;
return [x1 + calculateControlOffset(x2 - x1, c), y1];
case Position.Top:
{
ctX = x1;
ctY = y1 - calculateControlOffset(y1 - y2, c);
}
break;
return [x1, y1 - calculateControlOffset(y1 - y2, c)];
case Position.Bottom:
{
ctX = x1;
ctY = y1 + calculateControlOffset(y2 - y1, c);
}
break;
return [x1, y1 + calculateControlOffset(y2 - y1, c)];
}
return [ctX, ctY];
}
export function getBezierPath({
@@ -69,7 +53,7 @@ export function getBezierPath({
targetY,
targetPosition = Position.Top,
curvature = 0.25,
}: GetBezierPathParams): [string, number, number, number, number] {
}: GetBezierPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] {
const [sourceControlX, sourceControlY] = getControlWithCurvature({
pos: sourcePosition,
x1: sourceX,
@@ -86,7 +70,7 @@ export function getBezierPath({
y2: sourceY,
c: curvature,
});
const [centerX, centerY, offsetX, offsetY] = getBezierEdgeCenter({
const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({
sourceX,
sourceY,
targetX,
@@ -99,8 +83,8 @@ export function getBezierPath({
return [
`M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`,
centerX,
centerY,
labelX,
labelY,
offsetX,
offsetY,
];
@@ -1,4 +1,4 @@
import { FC, MouseEvent as ReactMouseEvent, SVGAttributes } from 'react';
import type { FC, MouseEvent as ReactMouseEvent, SVGAttributes } from 'react';
import cc from 'classcat';
import { Position } from '../../types';
@@ -1,4 +1,5 @@
import { memo, useRef, useState, useEffect, FC, PropsWithChildren } from 'react';
import { memo, useRef, useState, useEffect } from 'react';
import type { FC, PropsWithChildren } from 'react';
import cc from 'classcat';
import { EdgeTextProps, Rect } from '../../types';
@@ -41,6 +42,7 @@ const EdgeText: FC<PropsWithChildren<EdgeTextProps>> = ({
<g
transform={`translate(${x - edgeTextBbox.width / 2} ${y - edgeTextBbox.height / 2})`}
className={edgeTextClasses}
visibility={edgeTextBbox.width ? 'visible' : 'hidden'}
{...rest}
>
{labelShowBg && (
@@ -1,7 +1,9 @@
import { memo } from 'react';
import { EdgeProps, Position } from '../../types';
import BaseEdge from './BaseEdge';
import { getBezierEdgeCenter } from './utils';
import { Position } from '../../types';
import type { EdgeProps } from '../../types';
export interface GetSimpleBezierPathParams {
sourceX: number;
@@ -21,24 +23,11 @@ interface GetControlParams {
}
function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] {
let ctX: number, ctY: number;
switch (pos) {
case Position.Left:
case Position.Right:
{
ctX = 0.5 * (x1 + x2);
ctY = y1;
}
break;
case Position.Top:
case Position.Bottom:
{
ctX = x1;
ctY = 0.5 * (y1 + y2);
}
break;
if (pos === Position.Left || pos === Position.Right) {
return [0.5 * (x1 + x2), y1];
}
return [ctX, ctY];
return [x1, 0.5 * (y1 + y2)];
}
export function getSimpleBezierPath({
@@ -48,7 +37,7 @@ export function getSimpleBezierPath({
targetX,
targetY,
targetPosition = Position.Top,
}: GetSimpleBezierPathParams): [string, number, number, number, number] {
}: GetSimpleBezierPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] {
const [sourceControlX, sourceControlY] = getControl({
pos: sourcePosition,
x1: sourceX,
@@ -63,7 +52,7 @@ export function getSimpleBezierPath({
x2: sourceX,
y2: sourceY,
});
const [centerX, centerY, offsetX, offsetY] = getBezierEdgeCenter({
const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({
sourceX,
sourceY,
targetX,
@@ -76,8 +65,8 @@ export function getSimpleBezierPath({
return [
`M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`,
centerX,
centerY,
labelX,
labelY,
offsetX,
offsetY,
];
@@ -1,8 +1,9 @@
import { memo } from 'react';
import { SmoothStepEdgeProps, Position, XYPosition } from '../../types';
import BaseEdge from './BaseEdge';
import { getSimpleEdgeCenter } from './utils';
import { getEdgeCenter } from './utils';
import { Position } from '../../types';
import type { SmoothStepEdgeProps, XYPosition } from '../../types';
export interface GetSmoothStepPathParams {
sourceX: number;
@@ -72,7 +73,7 @@ function getPoints({
let points: XYPosition[] = [];
let centerX, centerY;
const [defaultCenterX, defaultCenterY, defaultOffsetX, defaultOffsetY] = getSimpleEdgeCenter({
const [defaultCenterX, defaultCenterY, defaultOffsetX, defaultOffsetY] = getEdgeCenter({
sourceX: source.x,
sourceY: source.y,
targetX: target.x,
@@ -170,7 +171,7 @@ export function getSmoothStepPath({
centerX,
centerY,
offset = 20,
}: GetSmoothStepPathParams): [string, number, number, number, number] {
}: GetSmoothStepPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] {
const [points, labelX, labelY, offsetX, offsetY] = getPoints({
source: { x: sourceX, y: sourceY },
sourcePosition,
@@ -1,7 +1,7 @@
import { memo, useMemo } from 'react';
import { SmoothStepEdgeProps } from '../../types';
import SmoothStepEdge from './SmoothStepEdge';
import type { SmoothStepEdgeProps } from '../../types';
const StepEdge = memo((props: SmoothStepEdgeProps) => (
<SmoothStepEdge
@@ -1,8 +1,8 @@
import { memo } from 'react';
import BaseEdge from './BaseEdge';
import { EdgeProps } from '../../types';
import { getSimpleEdgeCenter } from './utils';
import { getEdgeCenter } from './utils';
import type { EdgeProps } from '../../types';
export type GetStraightPathParams = {
sourceX: number;
@@ -16,15 +16,15 @@ export function getStraightPath({
sourceY,
targetX,
targetY,
}: GetStraightPathParams): [string, number, number, number, number] {
const [centerX, centerY, offsetX, offsetY] = getSimpleEdgeCenter({
}: GetStraightPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] {
const [labelX, labelY, offsetX, offsetY] = getEdgeCenter({
sourceX,
sourceY,
targetX,
targetY,
});
return [`M ${sourceX},${sourceY}L ${targetX},${targetY}`, centerX, centerY, offsetX, offsetY];
return [`M ${sourceX},${sourceY}L ${targetX},${targetY}`, labelX, labelY, offsetX, offsetY];
}
const StraightEdge = memo(
+2 -2
View File
@@ -1,7 +1,7 @@
import { MouseEvent as ReactMouseEvent } from 'react';
import { StoreApi } from 'zustand';
import { Edge, MarkerType, ReactFlowState } from '../../types';
import type { Edge, MarkerType, ReactFlowState } from '../../types';
export const getMarkerEnd = (markerType?: MarkerType, markerEndId?: string): string => {
if (typeof markerEndId !== 'undefined' && markerEndId) {
@@ -28,7 +28,7 @@ export function getMouseHandler(
}
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
export function getSimpleEdgeCenter({
export function getEdgeCenter({
sourceX,
sourceY,
targetX,
@@ -1,4 +1,5 @@
import { memo, ComponentType, useState, useMemo, KeyboardEvent, useRef } from 'react';
import { memo, useState, useMemo, useRef } from 'react';
import type { ComponentType, KeyboardEvent } from 'react';
import cc from 'classcat';
import { useStoreApi } from '../../hooks/useStore';
@@ -7,8 +8,8 @@ import { handleMouseDown } from '../Handle/handler';
import { EdgeAnchor } from './EdgeAnchor';
import { getMarkerId } from '../../utils/graph';
import { getMouseHandler } from './utils';
import { EdgeProps, WrapEdgeProps, Connection } from '../../types';
import { elementSelectionKeys } from '../../utils';
import type { EdgeProps, WrapEdgeProps, Connection } from '../../types';
export default (EdgeComponent: ComponentType<EdgeProps>) => {
const EdgeWrapper = ({
@@ -51,7 +52,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
markerStart,
rfId,
ariaLabel,
disableKeyboardA11y,
isFocusable,
pathOptions,
interactionWidth,
}: WrapEdgeProps): JSX.Element | null => {
@@ -158,12 +159,12 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
onMouseEnter={onEdgeMouseEnter}
onMouseMove={onEdgeMouseMove}
onMouseLeave={onEdgeMouseLeave}
onKeyDown={disableKeyboardA11y ? undefined : onKeyDown}
tabIndex={disableKeyboardA11y ? undefined : 0}
role={disableKeyboardA11y ? undefined : 'button'}
onKeyDown={isFocusable ? onKeyDown : undefined}
tabIndex={isFocusable ? 0 : undefined}
role={isFocusable ? 'button' : undefined}
data-testid={`rf__edge-${id}`}
aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`}
aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_EDGE_DESC_KEY}-${rfId}`}
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
ref={edgeRef}
>
{!updating && (
@@ -1,8 +1,9 @@
import { MouseEvent as ReactMouseEvent } from 'react';
import type { MouseEvent as ReactMouseEvent } from 'react';
import { StoreApi } from 'zustand';
import { getHostForElement } from '../../utils';
import { OnConnect, ConnectionMode, Connection, HandleType, ReactFlowState } from '../../types';
import { ConnectionMode } from '../../types';
import type { OnConnect, Connection, HandleType, ReactFlowState } from '../../types';
type ValidConnectionFunc = (connection: Connection) => boolean;
@@ -4,10 +4,11 @@ import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore';
import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
import { checkElementBelowIsValid, handleMouseDown } from './handler';
import { getHostForElement } from '../../utils';
import { addEdge } from '../../utils/graph';
import { Position } from '../../types';
import type { HandleProps, Connection, ReactFlowState } from '../../types';
const alwaysValid = () => true;
@@ -1,7 +1,8 @@
import { memo } from 'react';
import Handle from '../../components/Handle';
import { NodeProps, Position } from '../../types';
import { Position } from '../../types';
import type { NodeProps } from '../../types';
const DefaultNode = ({
data,
@@ -1,7 +1,8 @@
import { memo } from 'react';
import Handle from '../../components/Handle';
import { NodeProps, Position } from '../../types';
import { Position } from '../../types';
import type { NodeProps } from '../../types';
const InputNode = ({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) => (
<>
@@ -1,7 +1,8 @@
import { memo } from 'react';
import Handle from '../../components/Handle';
import { NodeProps, Position } from '../../types';
import { Position } from '../../types';
import type { NodeProps } from '../../types';
const OutputNode = ({ data, isConnectable, targetPosition = Position.Top }: NodeProps) => (
<>
+2 -1
View File
@@ -1,8 +1,9 @@
import { MouseEvent } from 'react';
import { StoreApi } from 'zustand';
import { HandleElement, Node, NodeOrigin, Position, ReactFlowState } from '../../types';
import { getDimensions } from '../../utils';
import { Position } from '../../types';
import type { HandleElement, Node, NodeOrigin, ReactFlowState } from '../../types';
export const getHandleBounds = (
selector: string,
+33 -13
View File
@@ -1,4 +1,5 @@
import { useEffect, useRef, memo, ComponentType, MouseEvent, KeyboardEvent } from 'react';
import { useEffect, useRef, memo } from 'react';
import type { ComponentType, MouseEvent, KeyboardEvent } from 'react';
import cc from 'classcat';
import { useStoreApi } from '../../hooks/useStore';
@@ -7,14 +8,14 @@ import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
import useDrag from '../../hooks/useDrag';
import useUpdateNodePositions from '../../hooks/useUpdateNodePositions';
import { getMouseHandler, handleNodeClick } from './utils';
import { NodeProps, WrapNodeProps, XYPosition } from '../../types';
import { elementSelectionKeys } from '../../utils';
import type { NodeProps, WrapNodeProps, XYPosition } from '../../types';
export const arrowKeyDiffs: Record<string, XYPosition> = {
ArrowUp: { x: 0, y: -10 },
ArrowDown: { x: 0, y: 10 },
ArrowLeft: { x: -10, y: 0 },
ArrowRight: { x: 10, y: 0 },
ArrowUp: { x: 0, y: -1 },
ArrowDown: { x: 0, y: 1 },
ArrowLeft: { x: -1, y: 0 },
ArrowRight: { x: 1, y: 0 },
};
export default (NodeComponent: ComponentType<NodeProps>) => {
@@ -38,6 +39,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
isDraggable,
isSelectable,
isConnectable,
isFocusable,
selectNodesOnDrag,
sourcePosition,
targetPosition,
@@ -82,6 +84,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
};
const onKeyDown = (event: KeyboardEvent) => {
const { snapGrid, snapToGrid } = store.getState();
if (elementSelectionKeys.includes(event.key) && isSelectable) {
const unselect = event.key === 'Escape';
if (unselect) {
@@ -92,14 +95,28 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
store,
unselect,
});
} else if (selected && Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) {
} else if (
!disableKeyboardA11y &&
isDraggable &&
selected &&
Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)
) {
store.setState({
ariaLiveMessage: `Moved selected node ten pixels ${event.key
ariaLiveMessage: `Moved selected node ${event.key
.replace('Arrow', '')
.toLowerCase()}. New position, x: ${~~xPos}, y: ${~~yPos}`,
});
updatePositions(arrowKeyDiffs[event.key]);
// by default a node moves 5px on each key press, or 20px if shift is pressed
// if snap grid is enabled, we use that for the velocity.
const xVelo = snapToGrid ? snapGrid[0] : 5;
const yVelo = snapToGrid ? snapGrid[1] : 5;
const factor = event.shiftKey ? 4 : 1;
updatePositions({
x: arrowKeyDiffs[event.key].x * xVelo * factor,
y: arrowKeyDiffs[event.key].y * yVelo * factor,
});
}
};
@@ -151,13 +168,16 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
className={cc([
'react-flow__node',
`react-flow__node-${type}`,
{
// this is overwritable by passing `nopan` as a class name
[noPanClassName]: isDraggable,
},
className,
{
selected,
selectable: isSelectable,
parent: isParent,
dragging,
[noPanClassName]: isDraggable,
},
])}
ref={nodeRef}
@@ -176,9 +196,9 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
onContextMenu={onContextMenuHandler}
onClick={onSelectNodeHandler}
onDoubleClick={onDoubleClickHandler}
onKeyDown={disableKeyboardA11y ? undefined : onKeyDown}
tabIndex={disableKeyboardA11y ? undefined : 0}
role={disableKeyboardA11y ? undefined : 'button'}
onKeyDown={isFocusable ? onKeyDown : undefined}
tabIndex={isFocusable ? 0 : undefined}
role={isFocusable ? 'button' : undefined}
aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`}
aria-label={ariaLabel}
>
@@ -3,16 +3,17 @@
* made a selection with on or several nodes
*/
import { memo, useRef, MouseEvent, KeyboardEvent, useEffect } from 'react';
import { memo, useRef, useEffect } from 'react';
import type { MouseEvent, KeyboardEvent } from 'react';
import cc from 'classcat';
import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { Node, ReactFlowState } from '../../types';
import { getRectOfNodes } from '../../utils/graph';
import useDrag from '../../hooks/useDrag';
import { arrowKeyDiffs } from '../Nodes/wrapNode';
import useUpdateNodePositions from '../../hooks/useUpdateNodePositions';
import type { Node, ReactFlowState } from '../../types';
export interface NodesSelectionProps {
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
+12 -4
View File
@@ -1,18 +1,26 @@
import { HTMLAttributes, ReactNode } from 'react';
import type { HTMLAttributes, ReactNode } from 'react';
import cc from 'classcat';
import { PanelPosition } from '../../types';
import { useStore } from '../../hooks/useStore';
import type { PanelPosition, ReactFlowState } from '../../types';
export type PanelProps = HTMLAttributes<HTMLDivElement> & {
position: PanelPosition;
children: ReactNode;
};
function Panel({ position, children, className, ...rest }: PanelProps) {
const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all');
function Panel({ position, children, className, style, ...rest }: PanelProps) {
const pointerEvents = useStore(selector);
const positionClasses = `${position}`.split('-');
return (
<div className={cc(['react-flow__panel', className, ...positionClasses])} {...rest}>
<div
className={cc(['react-flow__panel', className, ...positionClasses])}
style={{ ...style, pointerEvents }}
{...rest}
>
{children}
</div>
);
@@ -1,9 +1,10 @@
import { FC, PropsWithChildren, useRef } from 'react';
import { useRef } from 'react';
import type { FC, PropsWithChildren } from 'react';
import { StoreApi } from 'zustand';
import { Provider } from '../../contexts/RFStoreContext';
import { createRFStore } from '../../store';
import { ReactFlowState } from '../../types';
import type { ReactFlowState } from '../../types';
const ReactFlowProvider: FC<PropsWithChildren> = ({ children }) => {
const storeRef = useRef<StoreApi<ReactFlowState> | null>(null);
@@ -1,12 +1,12 @@
import { memo, useEffect } from 'react';
import shallow from 'zustand/shallow';
import { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types';
import { useStore, useStoreApi } from '../../hooks/useStore';
import type { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types';
interface SelectionListenerProps {
onSelectionChange: OnSelectionChangeFunc;
}
type SelectionListenerProps = {
onSelectionChange?: OnSelectionChangeFunc;
};
const selector = (s: ReactFlowState) => ({
selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected),
@@ -15,30 +15,42 @@ const selector = (s: ReactFlowState) => ({
type SelectorSlice = ReturnType<typeof selector>;
function areEqual(objA: SelectorSlice, objB: SelectorSlice) {
const selectedNodeIdsA = objA.selectedNodes.map((n: Node) => n.id);
const selectedNodeIdsB = objB.selectedNodes.map((n: Node) => n.id);
const selectId = (obj: Node | Edge) => obj.id;
const selectedEdgeIdsA = objA.selectedEdges.map((e: Edge) => e.id);
const selectedEdgeIdsB = objB.selectedEdges.map((e: Edge) => e.id);
return shallow(selectedNodeIdsA, selectedNodeIdsB) && shallow(selectedEdgeIdsA, selectedEdgeIdsB);
function areEqual(a: SelectorSlice, b: SelectorSlice) {
return (
shallow(a.selectedNodes.map(selectId), b.selectedNodes.map(selectId)) &&
shallow(a.selectedEdges.map(selectId), b.selectedEdges.map(selectId))
);
}
// This is just a helper component for calling the onSelectionChange listener.
// @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component?
function SelectionListener({ onSelectionChange }: SelectionListenerProps) {
const SelectionListener = memo(({ onSelectionChange }: SelectionListenerProps) => {
const store = useStoreApi();
const { selectedNodes, selectedEdges } = useStore(selector, areEqual);
useEffect(() => {
const params = { nodes: selectedNodes, edges: selectedEdges };
onSelectionChange(params);
onSelectionChange?.(params);
store.getState().onSelectionChange?.(params);
}, [selectedNodes, selectedEdges]);
}, [selectedNodes, selectedEdges, onSelectionChange]);
return null;
});
SelectionListener.displayName = 'SelectionListener';
const changeSelector = (s: ReactFlowState) => !!s.onSelectionChange;
function Wrapper({ onSelectionChange }: SelectionListenerProps) {
const storeHasSelectionChange = useStore(changeSelector);
if (onSelectionChange || storeHasSelectionChange) {
return <SelectionListener onSelectionChange={onSelectionChange} />;
}
return null;
}
export default memo(SelectionListener);
export default Wrapper;
@@ -3,7 +3,7 @@ import { StoreApi } from 'zustand';
import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { Node, Edge, ReactFlowState, CoordinateExtent, ReactFlowProps, ReactFlowStore } from '../../types';
import type { Node, Edge, ReactFlowState, CoordinateExtent, ReactFlowProps, ReactFlowStore } from '../../types';
type StoreUpdaterProps = Pick<
ReactFlowProps,
@@ -18,6 +18,8 @@ type StoreUpdaterProps = Pick<
| 'onClickConnectEnd'
| 'nodesDraggable'
| 'nodesConnectable'
| 'nodesFocusable'
| 'edgesFocusable'
| 'minZoom'
| 'maxZoom'
| 'nodeExtent'
@@ -42,8 +44,7 @@ type StoreUpdaterProps = Pick<
| 'onSelectionDragStop'
| 'noPanClassName'
| 'nodeOrigin'
| 'id'
>;
> & { rfId: string };
const selector = (s: ReactFlowState) => ({
setNodes: s.setNodes,
@@ -89,6 +90,8 @@ const StoreUpdater = ({
onClickConnectEnd,
nodesDraggable,
nodesConnectable,
nodesFocusable,
edgesFocusable,
minZoom,
maxZoom,
nodeExtent,
@@ -113,7 +116,7 @@ const StoreUpdater = ({
onSelectionDragStop,
noPanClassName,
nodeOrigin,
id,
rfId,
}: StoreUpdaterProps) => {
const {
setNodes,
@@ -145,6 +148,8 @@ const StoreUpdater = ({
useDirectStoreUpdater('onClickConnectEnd', onClickConnectEnd, store.setState);
useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState);
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
useDirectStoreUpdater('snapGrid', snapGrid, store.setState);
@@ -163,7 +168,7 @@ const StoreUpdater = ({
useDirectStoreUpdater('onSelectionDragStop', onSelectionDragStop, store.setState);
useDirectStoreUpdater('noPanClassName', noPanClassName, store.setState);
useDirectStoreUpdater('nodeOrigin', nodeOrigin, store.setState);
useDirectStoreUpdater('rfId', id, store.setState);
useDirectStoreUpdater('rfId', rfId, store.setState);
useStoreUpdater<Node[]>(nodes, setNodes);
useStoreUpdater<Edge[]>(edges, setEdges);
@@ -7,8 +7,8 @@ import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { getSelectionChanges } from '../../utils/changes';
import { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types';
import { getConnectedEdges, getNodesInside } from '../../utils/graph';
import type { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types';
type SelectionRect = Rect & {
startX: number;
@@ -1,16 +1,18 @@
import { memo, useCallback } from 'react';
import { useStore } from '../../hooks/useStore';
import { EdgeMarker, ReactFlowState } from '../../types';
import { getMarkerId } from '../../utils/graph';
import { useMarkerSymbol } from './MarkerSymbols';
interface MarkerProps extends EdgeMarker {
import type { EdgeMarker, ReactFlowState } from '../../types';
type MarkerProps = EdgeMarker & {
id: string;
}
interface MarkerDefinitionsProps {
};
type MarkerDefinitionsProps = {
defaultColor: string;
rfId?: string;
}
};
const Marker = ({
id,
@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import { MarkerType, EdgeMarker } from '../../types';
import { devWarn } from '../../utils';
import { MarkerType } from '../../types';
import type { EdgeMarker } from '../../types';
type SymbolProps = Omit<EdgeMarker, 'type'>;
@@ -8,44 +8,45 @@ import ConnectionLine from '../../components/ConnectionLine/index';
import MarkerDefinitions from './MarkerDefinitions';
import { getEdgePositions, getHandle, getNodeData } from './utils';
import { Position, Edge, ConnectionMode, ReactFlowState } from '../../types';
import { GraphViewProps } from '../GraphView';
import { devWarn } from '../../utils';
import { ConnectionMode, Position } from '../../types';
import type { Edge, ReactFlowState } from '../../types';
interface EdgeRendererProps
extends Pick<
GraphViewProps,
| 'edgeTypes'
| 'connectionLineType'
| 'connectionLineType'
| 'connectionLineStyle'
| 'connectionLineComponent'
| 'connectionLineContainerStyle'
| 'connectionLineContainerStyle'
| 'onEdgeClick'
| 'onEdgeDoubleClick'
| 'defaultMarkerColor'
| 'onlyRenderVisibleElements'
| 'onEdgeUpdate'
| 'onEdgeContextMenu'
| 'onEdgeMouseEnter'
| 'onEdgeMouseMove'
| 'onEdgeMouseLeave'
| 'onEdgeUpdateStart'
| 'onEdgeUpdateEnd'
| 'edgeUpdaterRadius'
| 'noPanClassName'
| 'elevateEdgesOnSelect'
| 'rfId'
| 'disableKeyboardA11y'
> {
type EdgeRendererProps = Pick<
GraphViewProps,
| 'edgeTypes'
| 'connectionLineType'
| 'connectionLineType'
| 'connectionLineStyle'
| 'connectionLineComponent'
| 'connectionLineContainerStyle'
| 'connectionLineContainerStyle'
| 'onEdgeClick'
| 'onEdgeDoubleClick'
| 'defaultMarkerColor'
| 'onlyRenderVisibleElements'
| 'onEdgeUpdate'
| 'onEdgeContextMenu'
| 'onEdgeMouseEnter'
| 'onEdgeMouseMove'
| 'onEdgeMouseLeave'
| 'onEdgeUpdateStart'
| 'onEdgeUpdateEnd'
| 'edgeUpdaterRadius'
| 'noPanClassName'
| 'elevateEdgesOnSelect'
| 'rfId'
| 'disableKeyboardA11y'
> & {
elevateEdgesOnSelect: boolean;
}
};
const selector = (s: ReactFlowState) => ({
connectionNodeId: s.connectionNodeId,
connectionHandleType: s.connectionHandleType,
nodesConnectable: s.nodesConnectable,
edgesFocusable: s.edgesFocusable,
elementsSelectable: s.elementsSelectable,
width: s.width,
height: s.height,
@@ -58,6 +59,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
connectionNodeId,
connectionHandleType,
nodesConnectable,
edgesFocusable,
elementsSelectable,
width,
height,
@@ -119,6 +121,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle || null);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
if (!sourceHandle || !targetHandle) {
devWarn(
@@ -181,7 +184,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
rfId={props.rfId}
ariaLabel={edge.ariaLabel}
disableKeyboardA11y={props.disableKeyboardA11y}
isFocusable={isFocusable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
interactionWidth={edge.interactionWidth}
/>
@@ -1,19 +1,20 @@
import { ComponentType } from 'react';
import type { ComponentType } from 'react';
import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges';
import wrapEdge from '../../components/Edges/wrapEdge';
import {
import { internalsSymbol, rectToBox } from '../../utils';
import { Position } from '../../types';
import type {
EdgeProps,
EdgeTypes,
EdgeTypesWrapped,
HandleElement,
NodeHandleBounds,
Node,
Position,
Rect,
Transform,
XYPosition,
} from '../../types';
import { internalsSymbol, rectToBox } from '../../utils';
export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped;
@@ -1,4 +1,4 @@
import { MouseEvent } from 'react';
import type { MouseEvent } from 'react';
import cc from 'classcat';
import { useStore } from '../../hooks/useStore';
@@ -1,4 +1,5 @@
import { memo, ReactNode, WheelEvent, MouseEvent } from 'react';
import { memo } from 'react';
import type { ReactNode, WheelEvent, MouseEvent } from 'react';
import { useStore, useStoreApi } from '../../hooks/useStore';
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
@@ -8,8 +9,7 @@ import ZoomPane from '../ZoomPane';
import UserSelection from '../../components/UserSelection';
import NodesSelection from '../../components/NodesSelection';
import Pane from './Pane';
import { ReactFlowState } from '../../types';
import type { ReactFlowState } from '../../types';
export type FlowRendererProps = Omit<
GraphViewProps,
+28 -32
View File
@@ -5,39 +5,35 @@ import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer';
import ViewportWrapper from '../Viewport';
import useOnInitHandler from '../../hooks/useOnInitHandler';
import {
NodeTypesWrapped,
EdgeTypesWrapped,
ConnectionLineType,
KeyCode,
ReactFlowProps,
Viewport,
CoordinateExtent,
NodeOrigin,
} from '../../types';
import type { EdgeTypesWrapped, NodeTypesWrapped, ReactFlowProps } from '../../types';
export interface GraphViewProps
extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges' | 'nodeTypes' | 'edgeTypes'> {
nodeTypes: NodeTypesWrapped;
edgeTypes: EdgeTypesWrapped;
selectionKeyCode: KeyCode | null;
deleteKeyCode: KeyCode | null;
multiSelectionKeyCode: KeyCode | null;
connectionLineType: ConnectionLineType;
onlyRenderVisibleElements: boolean;
translateExtent: CoordinateExtent;
minZoom: number;
maxZoom: number;
defaultMarkerColor: string;
selectNodesOnDrag: boolean;
noDragClassName: string;
noWheelClassName: string;
noPanClassName: string;
defaultViewport: Viewport;
rfId: string;
disableKeyboardA11y: boolean;
nodeOrigin: NodeOrigin;
}
export type GraphViewProps = Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges' | 'nodeTypes' | 'edgeTypes'> &
Required<
Pick<
ReactFlowProps,
| 'selectionKeyCode'
| 'deleteKeyCode'
| 'multiSelectionKeyCode'
| 'connectionLineType'
| 'onlyRenderVisibleElements'
| 'translateExtent'
| 'minZoom'
| 'maxZoom'
| 'defaultMarkerColor'
| 'selectNodesOnDrag'
| 'noDragClassName'
| 'noDragClassName'
| 'noWheelClassName'
| 'noPanClassName'
| 'defaultViewport'
| 'disableKeyboardA11y'
| 'nodeOrigin'
>
> & {
nodeTypes: NodeTypesWrapped;
edgeTypes: EdgeTypesWrapped;
rfId: string;
};
const GraphView = ({
nodeTypes,
@@ -1,4 +1,5 @@
import { memo, useMemo, ComponentType, useEffect, useRef } from 'react';
import { memo, useMemo, useEffect, useRef } from 'react';
import type { ComponentType } from 'react';
import shallow from 'zustand/shallow';
import useVisibleNodes from '../../hooks/useVisibleNodes';
@@ -6,8 +7,9 @@ import { useStore } from '../../hooks/useStore';
import { clampPosition, devWarn, internalsSymbol } from '../../utils';
import { containerStyle } from '../../styles';
import { GraphViewProps } from '../GraphView';
import { Position, ReactFlowState, WrapNodeProps } from '../../types';
import { getPositionWithOrigin } from './utils';
import { Position } from '../../types';
import type { ReactFlowState, WrapNodeProps } from '../../types';
type NodeRendererProps = Pick<
GraphViewProps,
@@ -31,12 +33,16 @@ type NodeRendererProps = Pick<
const selector = (s: ReactFlowState) => ({
nodesDraggable: s.nodesDraggable,
nodesConnectable: s.nodesConnectable,
nodesFocusable: s.nodesFocusable,
elementsSelectable: s.elementsSelectable,
updateNodeDimensions: s.updateNodeDimensions,
});
const NodeRenderer = (props: NodeRendererProps) => {
const { nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions } = useStore(selector, shallow);
const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions } = useStore(
selector,
shallow
);
const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
const resizeObserverRef = useRef<ResizeObserver>();
@@ -82,6 +88,8 @@ const NodeRenderer = (props: NodeRendererProps) => {
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined'));
const clampedPosition = props.nodeExtent
? clampPosition(node.positionAbsolute, props.nodeExtent)
: node.positionAbsolute;
@@ -122,6 +130,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
isDraggable={isDraggable}
isSelectable={isSelectable}
isConnectable={isConnectable}
isFocusable={isFocusable}
resizeObserver={resizeObserver}
dragHandle={node.dragHandle}
zIndex={node[internalsSymbol]?.z ?? 0}
@@ -1,12 +1,12 @@
import { ComponentType } from 'react';
import type { ComponentType } from 'react';
import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
import GroupNode from '../../components/Nodes/GroupNode';
import wrapNode from '../../components/Nodes/wrapNode';
import { NodeTypes, NodeProps, NodeTypesWrapped, NodeOrigin, XYPosition } from '../../types';
import { devWarn } from '../../utils';
import type { NodeTypes, NodeProps, NodeTypesWrapped, NodeOrigin, XYPosition } from '../../types';
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped;
@@ -1,4 +1,4 @@
import { FC, PropsWithChildren } from 'react';
import type { FC, PropsWithChildren } from 'react';
import { useStoreApi } from '../../hooks/useStore';
import ReactFlowProvider from '../../components/ReactFlowProvider';
+22 -18
View File
@@ -1,4 +1,5 @@
import { CSSProperties, forwardRef } from 'react';
import { forwardRef } from 'react';
import type { CSSProperties } from 'react';
import cc from 'classcat';
import Attribution from '../../components/Attribution';
@@ -9,27 +10,24 @@ import OutputNode from '../../components/Nodes/OutputNode';
import GroupNode from '../../components/Nodes/GroupNode';
import SelectionListener from '../../components/SelectionListener';
import StoreUpdater from '../../components/StoreUpdater';
import {
ConnectionLineType,
ConnectionMode,
import A11yDescriptions from '../../components/A11yDescriptions';
import { createEdgeTypes } from '../EdgeRenderer/utils';
import { createNodeTypes } from '../NodeRenderer/utils';
import GraphView from '../GraphView';
import Wrapper from './Wrapper';
import { infiniteExtent } from '../../store/initialState';
import { useNodeOrEdgeTypes } from './utils';
import { ConnectionLineType, ConnectionMode, PanOnScrollMode } from '../../types';
import type {
EdgeTypes,
EdgeTypesWrapped,
NodeOrigin,
NodeTypes,
NodeTypesWrapped,
PanOnScrollMode,
ReactFlowProps,
ReactFlowRefType,
Viewport,
} from '../../types';
import { createEdgeTypes } from '../EdgeRenderer/utils';
import GraphView from '../GraphView';
import { createNodeTypes } from '../NodeRenderer/utils';
import { useNodeOrEdgeTypes } from './utils';
import Wrapper from './Wrapper';
import A11yDescriptions from '../../components/A11yDescriptions';
import { infiniteExtent } from '../../store/initialState';
const defaultNodeTypes: NodeTypes = {
input: InputNode,
@@ -108,7 +106,9 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
selectNodesOnDrag = true,
nodesDraggable,
nodesConnectable,
nodesFocusable,
nodeOrigin = initNodeOrigin,
edgesFocusable,
elementsSelectable,
defaultViewport = initDefaultViewport,
minZoom = 0.5,
@@ -154,13 +154,14 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
elevateEdgesOnSelect = false,
disableKeyboardA11y = false,
style,
id = '1',
id,
...rest
},
ref
) => {
const nodeTypesWrapped = useNodeOrEdgeTypes(nodeTypes, createNodeTypes) as NodeTypesWrapped;
const edgeTypesWrapped = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes) as EdgeTypesWrapped;
const rfId = id || '1';
return (
<div
@@ -169,6 +170,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
ref={ref}
className={cc(['react-flow', className])}
data-testid="rf__wrapper"
id={id}
>
<Wrapper>
<GraphView
@@ -228,7 +230,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
noWheelClassName={noWheelClassName}
noPanClassName={noPanClassName}
elevateEdgesOnSelect={elevateEdgesOnSelect}
rfId={id}
rfId={rfId}
disableKeyboardA11y={disableKeyboardA11y}
nodeOrigin={nodeOrigin}
nodeExtent={nodeExtent}
@@ -245,6 +247,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
onClickConnectEnd={onClickConnectEnd}
nodesDraggable={nodesDraggable}
nodesConnectable={nodesConnectable}
nodesFocusable={nodesFocusable}
edgesFocusable={edgesFocusable}
elementsSelectable={elementsSelectable}
minZoom={minZoom}
maxZoom={maxZoom}
@@ -269,12 +273,12 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
onSelectionDragStop={onSelectionDragStop}
noPanClassName={noPanClassName}
nodeOrigin={nodeOrigin}
id={id}
rfId={rfId}
/>
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
<SelectionListener onSelectionChange={onSelectionChange} />
{children}
<Attribution proOptions={proOptions} position={attributionPosition} />
{!disableKeyboardA11y && <A11yDescriptions rfId={id} />}
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} />
</Wrapper>
</div>
);
@@ -1,10 +1,10 @@
import { useMemo, useRef } from 'react';
import shallow from 'zustand/shallow';
import { EdgeTypes, EdgeTypesWrapped, NodeTypes, NodeTypesWrapped } from '../../types';
import { devWarn } from '../../utils';
import { CreateEdgeTypes } from '../EdgeRenderer/utils';
import { CreateNodeTypes } from '../NodeRenderer/utils';
import type { EdgeTypes, EdgeTypesWrapped, NodeTypes, NodeTypesWrapped } from '../../types';
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: NodeTypes, createTypes: CreateNodeTypes): NodeTypesWrapped;
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: EdgeTypes, createTypes: CreateEdgeTypes): EdgeTypesWrapped;
@@ -1,7 +1,7 @@
import { ReactNode } from 'react';
import type { ReactNode } from 'react';
import { useStore } from '../../hooks/useStore';
import { ReactFlowState } from '../../types';
import type { ReactFlowState } from '../../types';
const selector = (s: ReactFlowState) => `translate(${s.transform[0]}px,${s.transform[1]}px) scale(${s.transform[2]})`;
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useRef } from 'react';
import { D3ZoomEvent, zoom, zoomIdentity } from 'd3-zoom';
import { zoom, zoomIdentity } from 'd3-zoom';
import type { D3ZoomEvent } from 'd3-zoom';
import { select, pointer } from 'd3-selection';
import shallow from 'zustand/shallow';
@@ -8,9 +9,10 @@ import { clamp } from '../../utils';
import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { FlowRendererProps } from '../FlowRenderer';
import { containerStyle } from '../../styles';
import { Viewport, PanOnScrollMode, ReactFlowState } from '../../types';
import type { FlowRendererProps } from '../FlowRenderer';
import { PanOnScrollMode } from '../../types';
import type { Viewport, ReactFlowState } from '../../types';
type ZoomPaneProps = Omit<
FlowRendererProps,
+3 -2
View File
@@ -1,12 +1,13 @@
import { RefObject, useEffect, useRef, MouseEvent, useState, useCallback } from 'react';
import { useEffect, useRef, useState, useCallback } from 'react';
import type { RefObject, MouseEvent } from 'react';
import { drag } from 'd3-drag';
import { select } from 'd3-selection';
import type { D3DragEvent, SubjectPosition } from 'd3';
import { useStoreApi } from '../../hooks/useStore';
import { NodeDragItem, Node, SelectionDragHandler } from '../../types';
import { getDragItems, getEventHandlerParams, hasSelector, calcNextPosition } from './utils';
import { handleNodeClick } from '../../components/Nodes/utils';
import type { NodeDragItem, Node, SelectionDragHandler } from '../../types';
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
export type UseDragData = { dx: number; dy: number };
+2 -2
View File
@@ -1,7 +1,7 @@
import { RefObject } from 'react';
import type { RefObject } from 'react';
import { CoordinateExtent, Node, NodeDragItem, NodeInternals, XYPosition } from '../../types';
import { clampPosition, devWarn } from '../../utils';
import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, XYPosition } from '../../types';
export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean {
if (!node.parentNode) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { useStore } from '../hooks/useStore';
import { Edge, ReactFlowState } from '../types';
import type { Edge, ReactFlowState } from '../types';
const edgesSelector = (state: ReactFlowState) => state.edges;
@@ -2,9 +2,8 @@ import { useEffect } from 'react';
import { useStoreApi } from '../hooks/useStore';
import useKeyPress from './useKeyPress';
import { KeyCode } from '../types';
import type { KeyCode } from '../types';
import useReactFlow from './useReactFlow';
interface HookParams {
deleteKeyCode: KeyCode | null;
multiSelectionKeyCode: KeyCode | null;
@@ -19,13 +18,10 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
useEffect(() => {
if (deleteKeyPressed) {
const {
nodeInternals,
edges,
} = store.getState();
const { nodeInternals, edges } = store.getState();
const nodes = Array.from(nodeInternals.values());
const nodeIds = nodes.filter(node => node.selected).map(node => node.id);
const edgeIds = edges.filter(edge => edge.selected).map(edge => edge.id);
const nodeIds = nodes.filter((node) => node.selected).map((node) => node.id);
const edgeIds = edges.filter((edge) => edge.selected).map((edge) => edge.id);
deleteElements(nodeIds, edgeIds);
store.setState({ nodesSelectionActive: false });
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useMemo } from 'react';
import { KeyCode } from '../types';
import type { KeyCode } from '../types';
type Keys = Array<string>;
type PressedKeys = Set<string>;
+1 -1
View File
@@ -1,5 +1,5 @@
import { useStore } from '../hooks/useStore';
import { Node, ReactFlowState } from '../types';
import type { Node, ReactFlowState } from '../types';
const nodesSelector = (state: ReactFlowState) => Array.from(state.nodeInternals.values());
@@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useState, useCallback, SetStateAction, Dispatch } from 'react';
import { useState, useCallback } from 'react';
import type { SetStateAction, Dispatch } from 'react';
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
import { Node, NodeChange, Edge, EdgeChange } from '../types';
import type { Node, NodeChange, Edge, EdgeChange } from '../types';
type ApplyChanges<ItemType, ChangesType> = (changes: ChangesType[], items: ItemType[]) => ItemType[];
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
@@ -1,6 +1,6 @@
import { ReactFlowState } from '../types';
import { internalsSymbol } from '../utils';
import { useStore } from './useStore';
import type { ReactFlowState } from '../types';
const selector = (s: ReactFlowState) => {
if (s.nodeInternals.size === 0) {
+1 -1
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef } from 'react';
import useReactFlow from './useReactFlow';
import { OnInit } from '../types';
import type { OnInit } from '../types';
function useOnInitHandler(onInit: OnInit | undefined) {
const rfInstance = useReactFlow();
+1 -1
View File
@@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react';
import useViewportHelper from './useViewportHelper';
import { useStoreApi } from '../hooks/useStore';
import {
import type {
ReactFlowInstance,
Instance,
NodeAddChange,
+2 -1
View File
@@ -1,4 +1,5 @@
import { useEffect, MutableRefObject } from 'react';
import { useEffect } from 'react';
import type { MutableRefObject } from 'react';
import { useStoreApi } from '../hooks/useStore';
import { devWarn, getDimensions } from '../utils';
+3 -2
View File
@@ -1,8 +1,9 @@
import { useContext, useMemo } from 'react';
import { StoreApi, useStore as useZustandStore } from 'zustand';
import { useStore as useZustandStore } from 'zustand';
import type { StoreApi } from 'zustand';
import StoreContext from '../contexts/RFStoreContext';
import { ReactFlowState } from '../types';
import type { ReactFlowState } from '../types';
const errorMessage =
'[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#100';
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useStoreApi } from '../hooks/useStore';
import { UpdateNodeInternals } from '../types';
import type { UpdateNodeInternals } from '../types';
function useUpdateNodeInternals(): UpdateNodeInternals {
const store = useStoreApi();
@@ -2,24 +2,25 @@ import { useCallback } from 'react';
import { useStoreApi } from '../hooks/useStore';
import { calcNextPosition } from './useDrag/utils';
import { XYPosition } from '../types';
import type { XYPosition } from '../types';
function useUpdateNodePositions() {
const store = useStoreApi();
const updatePositions = useCallback((positionDiff: XYPosition) => {
const { nodeInternals, nodeExtent, updateNodePositions } = store.getState();
const { nodeInternals, nodeExtent, updateNodePositions, snapToGrid, snapGrid } = store.getState();
const selectedNodes = Array.from(nodeInternals.values()).filter((n) => n.selected);
const nodeUpdates = selectedNodes.map((n) => {
if (n.positionAbsolute) {
const updatedPos = calcNextPosition(
n,
{ x: n.positionAbsolute.x + positionDiff.x, y: n.positionAbsolute.y + positionDiff.y },
nodeInternals,
nodeExtent
);
const nextPosition = { x: n.positionAbsolute.x + positionDiff.x, y: n.positionAbsolute.y + positionDiff.y };
if (snapToGrid) {
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
}
const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent);
n.position = updatedPos.position;
n.positionAbsolute = updatedPos.positionAbsolute;
+1 -1
View File
@@ -1,7 +1,7 @@
import shallow from 'zustand/shallow';
import { useStore } from '../hooks/useStore';
import { Viewport, ReactFlowState } from '../types';
import type { Viewport, ReactFlowState } from '../types';
const viewportSelector = (state: ReactFlowState) => ({
x: state.transform[0],
+1 -1
View File
@@ -4,8 +4,8 @@ import shallow from 'zustand/shallow';
import { useStoreApi, useStore } from '../hooks/useStore';
import { pointToRendererPoint, getTransformForBounds, getD3Transition } from '../utils/graph';
import { ViewportHelperFunctions, ReactFlowState, XYPosition } from '../types';
import { fitView as fitViewStore } from '../store/utils';
import type { ViewportHelperFunctions, ReactFlowState, XYPosition } from '../types';
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
+1 -1
View File
@@ -2,8 +2,8 @@ import { useCallback } from 'react';
import { useStore } from '../hooks/useStore';
import { isEdgeVisible } from '../container/EdgeRenderer/utils';
import { ReactFlowState, NodeInternals, Edge } from '../types';
import { internalsSymbol, isNumeric } from '../utils';
import type { ReactFlowState, NodeInternals, Edge } from '../types';
const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
+1 -2
View File
@@ -2,8 +2,7 @@ import { useCallback } from 'react';
import { useStore } from '../hooks/useStore';
import { getNodesInside } from '../utils/graph';
import { ReactFlowState } from '../types';
import type { ReactFlowState } from '../types';
function useVisibleNodes(onlyRenderVisible: boolean) {
const nodes = useStore(
+10 -6
View File
@@ -1,8 +1,11 @@
import { createStore } from 'zustand';
import { clampPosition, getDimensions, internalsSymbol } from '../utils';
import { applyNodeChanges } from '../utils/changes';
import {
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
import { getHandleBounds } from '../components/Nodes/utils';
import { createNodeInternals, fitView, updateNodesAndEdgesSelections } from './utils';
import initialState from './initialState';
import type {
ReactFlowState,
Node,
Edge,
@@ -15,10 +18,6 @@ import {
NodeDragItem,
UnselectNodesAndEdgesParams,
} from '../types';
import { getHandleBounds } from '../components/Nodes/utils';
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
import { createNodeInternals, fitView, updateNodesAndEdgesSelections } from './utils';
import initialState from './initialState';
const createRFStore = () =>
createStore<ReactFlowState>((set, get) => ({
@@ -238,6 +237,11 @@ const createRFStore = () =>
nodeInternals: new Map(nodeInternals),
});
},
cancelConnection: () =>
set({
connectionNodeId: initialState.connectionNodeId,
connectionHandleId: initialState.connectionHandleId,
}),
reset: () => set({ ...initialState }),
}));
+4 -1
View File
@@ -1,4 +1,5 @@
import { CoordinateExtent, ReactFlowStore, ConnectionMode } from '../types';
import { ConnectionMode } from '../types';
import type { CoordinateExtent, ReactFlowStore } from '../types';
export const infiniteExtent: CoordinateExtent = [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
@@ -40,6 +41,8 @@ const initialState: ReactFlowStore = {
nodesDraggable: true,
nodesConnectable: true,
nodesFocusable: true,
edgesFocusable: true,
elementsSelectable: true,
fitViewOnInit: false,
fitViewOnInitDone: false,
+2 -2
View File
@@ -1,9 +1,9 @@
import { zoomIdentity } from 'd3-zoom';
import { StoreApi } from 'zustand';
import type { StoreApi } from 'zustand';
import { internalsSymbol, isNumeric } from '../utils';
import { getD3Transition, getRectOfNodes, getTransformForBounds } from '../utils/graph';
import {
import type {
Edge,
EdgeSelectionChange,
Node,
+1 -1
View File
@@ -1,4 +1,4 @@
import { CSSProperties } from 'react';
import type { CSSProperties } from 'react';
export const containerStyle: CSSProperties = {
position: 'absolute',
+5
View File
@@ -56,6 +56,11 @@
animation: dashdraw 0.5s linear infinite;
}
&.animated path.react-flow__edge-interaction {
stroke-dasharray: none;
animation: none;
}
&.inactive {
pointer-events: none;
}
+3 -3
View File
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { XYPosition, Dimensions } from './utils';
import { Node } from './nodes';
import { Edge } from './edges';
import type { XYPosition, Dimensions } from './utils';
import type { Node } from './nodes';
import type { Edge } from './edges';
export type NodeDimensionChange = {
id: string;
+24 -21
View File
@@ -1,6 +1,6 @@
import React, { CSSProperties, HTMLAttributes, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
import type { CSSProperties, HTMLAttributes, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
import {
import type {
OnSelectionChangeFunc,
NodeTypes,
EdgeTypes,
@@ -33,19 +33,17 @@ import {
SelectionDragHandler,
Viewport,
NodeOrigin,
EdgeMouseHandler,
HandleType,
} from '.';
import { HandleType } from './handles';
export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
nodes?: Node[];
edges?: Edge[];
defaultNodes?: Node[];
defaultEdges?: Edge[];
defaultEdgeOptions?: DefaultEdgeOptions;
onNodesChange?: OnNodesChange;
onEdgesChange?: OnEdgesChange;
onNodeClick?: NodeMouseHandler;
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
onNodeDoubleClick?: NodeMouseHandler;
onNodeMouseEnter?: NodeMouseHandler;
onNodeMouseMove?: NodeMouseHandler;
@@ -54,8 +52,23 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
onNodeDragStart?: NodeDragHandler;
onNodeDrag?: NodeDragHandler;
onNodeDragStop?: NodeDragHandler;
onEdgeClick?: (event: ReactMouseEvent, node: Edge) => void;
onEdgeUpdate?: OnEdgeUpdateFunc;
onEdgeContextMenu?: EdgeMouseHandler;
onEdgeMouseEnter?: EdgeMouseHandler;
onEdgeMouseMove?: EdgeMouseHandler;
onEdgeMouseLeave?: EdgeMouseHandler;
onEdgeDoubleClick?: EdgeMouseHandler;
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
onNodesChange?: OnNodesChange;
onEdgesChange?: OnEdgesChange;
onNodesDelete?: OnNodesDelete;
onEdgesDelete?: OnEdgesDelete;
onSelectionDragStart?: SelectionDragHandler;
onSelectionDrag?: SelectionDragHandler;
onSelectionDragStop?: SelectionDragHandler;
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: Node[]) => void;
onConnect?: OnConnect;
onConnectStart?: OnConnectStart;
onConnectEnd?: OnConnectEnd;
@@ -66,10 +79,6 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
onMoveStart?: OnMoveStart;
onMoveEnd?: OnMoveEnd;
onSelectionChange?: OnSelectionChangeFunc;
onSelectionDragStart?: SelectionDragHandler;
onSelectionDrag?: SelectionDragHandler;
onSelectionDragStop?: SelectionDragHandler;
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: Node[]) => void;
onPaneScroll?: (event?: WheelEvent) => void;
onPaneClick?: (event: ReactMouseEvent) => void;
onPaneContextMenu?: (event: ReactMouseEvent) => void;
@@ -78,11 +87,11 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
nodeTypes?: NodeTypes;
edgeTypes?: EdgeTypes;
connectionMode?: ConnectionMode;
connectionLineType?: ConnectionLineType;
connectionLineStyle?: CSSProperties;
connectionLineComponent?: ConnectionLineComponent;
connectionLineContainerStyle?: CSSProperties;
connectionMode?: ConnectionMode;
deleteKeyCode?: KeyCode | null;
selectionKeyCode?: KeyCode | null;
multiSelectionKeyCode?: KeyCode | null;
@@ -92,7 +101,9 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
onlyRenderVisibleElements?: boolean;
nodesDraggable?: boolean;
nodesConnectable?: boolean;
nodesFocusable?: boolean;
nodeOrigin?: NodeOrigin;
edgesFocusable?: boolean;
initNodeOrigin?: NodeOrigin;
elementsSelectable?: boolean;
selectNodesOnDrag?: boolean;
@@ -110,14 +121,6 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
panOnScrollSpeed?: number;
panOnScrollMode?: PanOnScrollMode;
zoomOnDoubleClick?: boolean;
onEdgeUpdate?: OnEdgeUpdateFunc;
onEdgeContextMenu?: (event: ReactMouseEvent, edge: Edge) => void;
onEdgeMouseEnter?: (event: ReactMouseEvent, edge: Edge) => void;
onEdgeMouseMove?: (event: ReactMouseEvent, edge: Edge) => void;
onEdgeMouseLeave?: (event: ReactMouseEvent, edge: Edge) => void;
onEdgeDoubleClick?: (event: ReactMouseEvent, edge: Edge) => void;
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
edgeUpdaterRadius?: number;
noDragClassName?: string;
noWheelClassName?: string;
@@ -129,6 +132,6 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
proOptions?: ProOptions;
elevateEdgesOnSelect?: boolean;
disableKeyboardA11y?: boolean;
}
};
export type ReactFlowRefType = HTMLDivElement;
+62 -80
View File
@@ -1,9 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { CSSProperties, ComponentType, HTMLAttributes, ReactNode } from 'react';
import { Connection } from './general';
import { HandleElement, HandleType } from './handles';
import { Node } from './nodes';
import { Position } from './utils';
import type { CSSProperties, ComponentType, HTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent } from 'react';
import { Position } from '.';
import type { Connection, HandleElement, HandleType, Node } from '.';
type EdgeLabelOptions = {
label?: string | ReactNode;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
labelBgPadding?: [number, number];
labelBgBorderRadius?: number;
};
// interface for the user edge items
type DefaultEdge<T = any> = {
@@ -13,12 +21,6 @@ type DefaultEdge<T = any> = {
target: string;
sourceHandle?: string | null;
targetHandle?: string | null;
label?: string | ReactNode;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
labelBgPadding?: [number, number];
labelBgBorderRadius?: number;
style?: CSSProperties;
animated?: boolean;
hidden?: boolean;
@@ -33,7 +35,8 @@ type DefaultEdge<T = any> = {
zIndex?: number;
ariaLabel?: string;
interactionWidth?: number;
};
focusable?: boolean;
} & EdgeLabelOptions;
export type SmoothStepPathOptions = {
offset?: number;
@@ -61,55 +64,7 @@ export type DefaultEdgeOptions = Omit<
'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'sourceNode' | 'targetNode'
>;
// props that get passed to a custom edge
export type EdgeProps<T = any> = {
id: string;
source: string;
target: string;
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
selected?: boolean;
animated?: boolean;
sourcePosition: Position;
targetPosition: Position;
label?: string | ReactNode;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
labelBgPadding?: [number, number];
labelBgBorderRadius?: number;
style?: CSSProperties;
data?: T;
sourceHandleId?: string | null;
targetHandleId?: string | null;
markerStart?: string;
markerEnd?: string;
// @TODO: how can we get better types for pathOptions?
pathOptions?: any;
interactionWidth?: number;
};
export type BaseEdgeProps = Pick<
EdgeProps,
| 'label'
| 'labelStyle'
| 'labelShowBg'
| 'labelBgStyle'
| 'labelBgPadding'
| 'labelBgBorderRadius'
| 'style'
| 'markerStart'
| 'markerEnd'
| 'interactionWidth'
> & {
labelX: number;
labelY: number;
path: string;
};
export type EdgeMouseHandler = (event: React.MouseEvent, edge: Edge) => void;
export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void;
export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle'> & {
onClick?: EdgeMouseHandler;
@@ -129,30 +84,57 @@ export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandl
onMouseMove?: EdgeMouseHandler;
onMouseLeave?: EdgeMouseHandler;
edgeUpdaterRadius?: number;
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge, handleType: HandleType) => void;
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
rfId?: string;
disableKeyboardA11y: boolean;
isFocusable: boolean;
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
};
export interface SmoothStepEdgeProps<T = any> extends EdgeProps<T> {
pathOptions?: SmoothStepPathOptions;
}
// props that get passed to a custom edge
export type EdgeProps<T = any> = Pick<
Edge<T>,
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'
> &
Pick<
WrapEdgeProps,
| 'sourceX'
| 'sourceY'
| 'targetX'
| 'targetY'
| 'sourcePosition'
| 'targetPosition'
| 'sourceHandleId'
| 'targetHandleId'
| 'interactionWidth'
> &
EdgeLabelOptions & {
markerStart?: string;
markerEnd?: string;
// @TODO: how can we get better types for pathOptions?
pathOptions?: any;
};
export interface BezierEdgeProps<T = any> extends EdgeProps<T> {
export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerStart' | 'markerEnd' | 'interactionWidth'> &
EdgeLabelOptions & {
labelX: number;
labelY: number;
path: string;
};
export type SmoothStepEdgeProps<T = any> = EdgeProps<T> & {
pathOptions?: SmoothStepPathOptions;
};
export type BezierEdgeProps<T = any> = EdgeProps<T> & {
pathOptions?: BezierPathOptions;
}
export interface EdgeTextProps extends HTMLAttributes<SVGElement> {
x: number;
y: number;
label?: string | ReactNode;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
labelBgPadding?: [number, number];
labelBgBorderRadius?: number;
}
};
export type EdgeTextProps = HTMLAttributes<SVGElement> &
EdgeLabelOptions & {
x: number;
y: number;
};
export enum ConnectionLineType {
Bezier = 'default',
@@ -179,7 +161,7 @@ export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void;
export interface EdgeMarker {
export type EdgeMarker = {
type: MarkerType;
color?: string;
width?: number;
@@ -187,7 +169,7 @@ export interface EdgeMarker {
markerUnits?: string;
orient?: string;
strokeWidth?: number;
}
};
export type EdgeMarkerType = string | EdgeMarker;
+14 -11
View File
@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { MouseEvent as ReactMouseEvent, ComponentType, MemoExoticComponent } from 'react';
import type { MouseEvent as ReactMouseEvent, ComponentType, MemoExoticComponent } from 'react';
import type { Selection as D3Selection, ZoomBehavior } from 'd3';
import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
import { NodeChange, EdgeChange } from './changes';
import {
import type { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
import type { NodeChange, EdgeChange } from './changes';
import type {
Node,
NodeInternals,
NodeDimensionUpdate,
@@ -15,10 +15,10 @@ import {
SelectionDragHandler,
NodeOrigin,
} from './nodes';
import { Edge, EdgeProps, WrapEdgeProps } from './edges';
import { HandleType, StartHandle } from './handles';
import { DefaultEdgeOptions } from '.';
import { ReactFlowInstance } from './instance';
import type { Edge, EdgeProps, WrapEdgeProps } from './edges';
import type { HandleType, StartHandle } from './handles';
import type { DefaultEdgeOptions } from '.';
import type { ReactFlowInstance } from './instance';
export type NodeTypes = { [key: string]: ComponentType<NodeProps> };
export type NodeTypesWrapped = { [key: string]: MemoExoticComponent<ComponentType<WrapNodeProps>> };
@@ -115,7 +115,7 @@ export type UnselectNodesAndEdgesParams = {
export type OnViewportChange = (viewport: Viewport) => void;
export interface ViewportHelperFunctions {
export type ViewportHelperFunctions = {
zoomIn: ZoomInOut;
zoomOut: ZoomInOut;
zoomTo: ZoomTo;
@@ -127,7 +127,7 @@ export interface ViewportHelperFunctions {
fitBounds: FitBounds;
project: Project;
viewportInitialized: boolean;
}
};
export type ReactFlowStore = {
rfId: string;
@@ -167,6 +167,8 @@ export type ReactFlowStore = {
nodesDraggable: boolean;
nodesConnectable: boolean;
nodesFocusable: boolean;
edgesFocusable: boolean;
elementsSelectable: boolean;
multiSelectionActive: boolean;
@@ -222,6 +224,7 @@ export type ReactFlowActions = {
setMaxZoom: (maxZoom: number) => void;
setTranslateExtent: (translateExtent: CoordinateExtent) => void;
setNodeExtent: (nodeExtent: CoordinateExtent) => void;
cancelConnection: () => void;
reset: () => void;
};
@@ -239,6 +242,6 @@ export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
export type ProOptions = {
account: string;
account?: string;
hideAttribution: boolean;
};
+1 -2
View File
@@ -1,5 +1,4 @@
import { XYPosition, Position, Dimensions } from './utils';
import { OnConnect, Connection } from './general';
import type { XYPosition, Position, Dimensions, OnConnect, Connection } from '.';
export type HandleType = 'source' | 'target';
+1 -3
View File
@@ -1,8 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-namespace */
import { ViewportHelperFunctions, Viewport } from './general';
import { Node } from './nodes';
import { Edge } from './edges';
import { ViewportHelperFunctions, Viewport, Node, Edge } from '.';
export type ReactFlowJsonObject<NodeData = any, EdgeData = any> = {
nodes: Node<NodeData>[];
+44 -57
View File
@@ -1,20 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import { XYPosition, Position, CoordinateExtent } from './utils';
import { HandleElement } from './handles';
import { internalsSymbol } from '../utils';
import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.';
// interface for the user node items
export interface Node<T = any> {
export type Node<T = any> = {
id: string;
position: XYPosition;
data: T;
type?: string;
style?: CSSProperties;
className?: string;
targetPosition?: Position;
sourcePosition?: Position;
targetPosition?: Position;
hidden?: boolean;
selected?: boolean;
dragging?: boolean;
@@ -31,6 +30,7 @@ export interface Node<T = any> {
expandParent?: boolean;
positionAbsolute?: XYPosition;
ariaLabel?: string;
focusable?: boolean;
// only used internally
[internalsSymbol]?: {
@@ -38,63 +38,50 @@ export interface Node<T = any> {
handleBounds?: NodeHandleBounds;
isParent?: boolean;
};
}
// props that get passed to a custom node
export interface NodeProps<T = any> {
id: string;
type: string;
data: T;
selected: boolean;
isConnectable: boolean;
xPos: number;
yPos: number;
dragging: boolean;
zIndex: number;
targetPosition?: Position;
sourcePosition?: Position;
dragHandle?: string;
}
};
export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void;
export type NodeDragHandler = (event: ReactMouseEvent, node: Node, nodes: Node[]) => void;
export type SelectionDragHandler = (event: ReactMouseEvent, nodes: Node[]) => void;
export interface WrapNodeProps<T = any> {
id: string;
type: string;
data: T;
selected: boolean;
isConnectable: boolean;
xPos: number;
yPos: number;
xPosOrigin: number;
yPosOrigin: number;
initialized: boolean;
isSelectable: boolean;
isDraggable: boolean;
selectNodesOnDrag: boolean;
onClick?: NodeMouseHandler;
onDoubleClick?: NodeMouseHandler;
onMouseEnter?: NodeMouseHandler;
onMouseMove?: NodeMouseHandler;
onMouseLeave?: NodeMouseHandler;
onContextMenu?: NodeMouseHandler;
style?: CSSProperties;
className?: string;
sourcePosition: Position;
targetPosition: Position;
hidden?: boolean;
resizeObserver: ResizeObserver | null;
dragHandle?: string;
zIndex: number;
isParent: boolean;
noDragClassName: string;
noPanClassName: string;
rfId: string;
disableKeyboardA11y: boolean;
ariaLabel?: string;
}
export type WrapNodeProps<T = any> = Pick<
Node<T>,
'id' | 'data' | 'style' | 'className' | 'dragHandle' | 'sourcePosition' | 'targetPosition' | 'hidden' | 'ariaLabel'
> &
Required<Pick<Node<T>, 'selected' | 'type' | 'zIndex'>> & {
isConnectable: boolean;
xPos: number;
yPos: number;
xPosOrigin: number;
yPosOrigin: number;
initialized: boolean;
isSelectable: boolean;
isDraggable: boolean;
isFocusable: boolean;
selectNodesOnDrag: boolean;
onClick?: NodeMouseHandler;
onDoubleClick?: NodeMouseHandler;
onMouseEnter?: NodeMouseHandler;
onMouseMove?: NodeMouseHandler;
onMouseLeave?: NodeMouseHandler;
onContextMenu?: NodeMouseHandler;
resizeObserver: ResizeObserver | null;
isParent: boolean;
noDragClassName: string;
noPanClassName: string;
rfId: string;
disableKeyboardA11y: boolean;
};
// props that get passed to a custom node
export type NodeProps<T = any> = Pick<
WrapNodeProps<T>,
'id' | 'data' | 'dragHandle' | 'type' | 'selected' | 'isConnectable' | 'xPos' | 'yPos' | 'zIndex'
> & {
dragging: boolean;
targetPosition?: Position;
sourcePosition?: Position;
};
export type NodeHandleBounds = {
source: HandleElement[] | null;
+1 -1
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Node, Edge, EdgeChange, NodeChange } from '../types';
import type { Node, Edge, EdgeChange, NodeChange } from '../types';
function handleParentExpand(res: any[], updateItem: any) {
const parent = res.find((e) => e.id === updateItem.parentNode);

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