toggle handle count
toggle handle position
diff --git a/example/src/UseZoomPanHelper/index.tsx b/example/src/UseZoomPanHelper/index.tsx
index a362eb06..55aeae65 100644
--- a/example/src/UseZoomPanHelper/index.tsx
+++ b/example/src/UseZoomPanHelper/index.tsx
@@ -1,42 +1,46 @@
-import React, { useState, useCallback } from 'react';
+import { useCallback } from 'react';
import ReactFlow, {
- removeElements,
+ Node,
addEdge,
Background,
MiniMap,
- useZoomPanHelper,
+ useReactFlow,
ReactFlowProvider,
- Elements,
- ElementId,
Connection,
Edge,
+ useNodesState,
+ useEdgesState,
} from 'react-flow-renderer';
-const initialElements: Elements = [
+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' },
+];
+
+const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
];
let id = 5;
-const getId = (): ElementId => `${id++}`;
+
+const getId = () => `${id++}`;
const UseZoomPanHelperFlow = () => {
- const [elements, setElements] = useState
(initialElements);
- const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
- const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els));
- const { project, setCenter, zoomIn, zoomOut } = useZoomPanHelper();
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+ const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
+ const { project, setCenter, zoomIn, zoomOut, fitView } = useReactFlow();
const onPaneClick = useCallback(
(evt) => {
const projectedPosition = project({ x: evt.clientX, y: evt.clientY - 40 });
- setElements((els) =>
- els.concat({
+ setNodes((nds) =>
+ nds.concat({
id: getId(),
position: projectedPosition,
data: {
@@ -48,25 +52,31 @@ const UseZoomPanHelperFlow = () => {
[project]
);
- const onElementClick = useCallback(
+ const onNodeClick = useCallback(
(_, element) => {
const { x, y } = element.position;
- setCenter(x, y, 1);
+ setCenter(x, y, { zoom: 1, duration: 1200 });
},
[setCenter]
);
return (
- zoomIn(1200)}>zoomIn
- zoomOut(0)}>zoomOut
+ zoomIn({ duration: 1200 })}>zoomIn
+ zoomOut({ duration: 0 })}>zoomOut
+ fitView({ duration: 1200, padding: 0.3 })}>fitView
diff --git a/example/src/Validation/index.tsx b/example/src/Validation/index.tsx
index d5209ed4..ba58b0b5 100644
--- a/example/src/Validation/index.tsx
+++ b/example/src/Validation/index.tsx
@@ -1,27 +1,27 @@
-import React, { useState, MouseEvent as ReactMouseEvent, FC } from 'react';
+import { MouseEvent as ReactMouseEvent, FC } from 'react';
import ReactFlow, {
addEdge,
Handle,
- OnLoadParams,
Connection,
Position,
- Elements,
+ Node,
Edge,
OnConnectStartParams,
NodeProps,
- NodeTypesType,
+ NodeTypes,
+ useNodesState,
+ useEdgesState,
} from 'react-flow-renderer';
import './validation.css';
-const initialElements: Elements = [
- { id: '0', type: 'custominput', position: { x: 0, y: 150 } },
- { id: 'A', type: 'customnode', position: { x: 250, y: 0 } },
- { id: 'B', type: 'customnode', position: { x: 250, y: 150 } },
- { id: 'C', type: 'customnode', position: { x: 250, y: 300 } },
+const initialNodes: Node[] = [
+ { id: '0', type: 'custominput', position: { x: 0, y: 150 }, data: null },
+ { id: 'A', type: 'customnode', position: { x: 250, y: 0 }, data: null },
+ { id: 'B', type: 'customnode', position: { x: 250, y: 150 }, data: null },
+ { id: 'C', type: 'customnode', position: { x: 250, y: 300 }, data: null },
];
-const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView();
const isValidConnection = (connection: Connection) => connection.target === 'B';
const onConnectStart = (_: ReactMouseEvent, { nodeId, handleType }: OnConnectStartParams) =>
console.log('on connect start', { nodeId, handleType });
@@ -43,31 +43,36 @@ const CustomNode: FC = ({ id }) => (
>
);
-const nodeTypes: NodeTypesType = {
+const nodeTypes: NodeTypes = {
custominput: CustomInput,
customnode: CustomNode,
};
-const HorizontalFlow = () => {
- const [elements, setElements] = useState(initialElements);
+const ValidationFlow = () => {
+ const [nodes, , onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
+
const onConnect = (params: Connection | Edge) => {
console.log('on connect', params);
- setElements((els) => addEdge(params, els));
+ setEdges((eds) => addEdge(params, eds));
};
return (
);
};
-export default HorizontalFlow;
+export default ValidationFlow;
diff --git a/example/src/index.tsx b/example/src/index.tsx
index 8371c0af..1208c967 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -1,89 +1,106 @@
-import React, { ChangeEvent } from 'react';
+import { ChangeEvent } from 'react';
import ReactDOM from 'react-dom';
-import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router-dom';
-
-import Overview from './Overview';
+import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import Basic from './Basic';
-import CustomNode from './CustomNode';
-import Stress from './Stress';
-import Interaction from './Interaction';
-import Empty from './Empty';
-import Edges from './Edges';
-import Validation from './Validation';
-import Provider from './Provider';
-import Hidden from './Hidden';
-import EdgeTypes from './EdgeTypes';
+import ControlledUncontrolled from './ControlledUncontrolled';
import CustomConnectionLine from './CustomConnectionLine';
+import CustomNode from './CustomNode';
+import DefaultNodes from './DefaultNodes';
+import DragHandle from './DragHandle';
+import DragNDrop from './DragNDrop';
+import Edges from './Edges';
+import EdgeTypes from './EdgeTypes';
+import Empty from './Empty';
import FloatingEdges from './FloatingEdges';
+import Hidden from './Hidden';
+import './index.css';
+import Interaction from './Interaction';
+import Layouting from './Layouting';
+import MultiFlows from './MultiFlows';
+import NestedNodes from './NestedNodes';
import NodeTypeChange from './NodeTypeChange';
import NodeTypesObjectChange from './NodeTypesObjectChange';
+import Overview from './Overview';
+import Provider from './Provider';
+import SaveRestore from './SaveRestore';
+import Stress from './Stress';
+import Subflow from './Subflow';
+import SwitchFlow from './Switch';
+import TouchDevice from './TouchDevice';
+import Undirectional from './Undirectional';
import UpdatableEdge from './UpdatableEdge';
import UpdateNode from './UpdateNode';
-import SaveRestore from './SaveRestore';
-import DragNDrop from './DragNDrop';
-import Layout from './Layouting';
-import SwitchFlows from './Switch';
-import UseZoomPanHelper from './UseZoomPanHelper';
import UseUpdateNodeInternals from './UseUpdateNodeInternals';
-import Undirectional from './Undirectional';
-import MultiFlows from './MultiFlows';
-import DragHandle from './DragHandle';
+import UseZoomPanHelper from './UseZoomPanHelper';
+import Validation from './Validation';
+
-import './index.css';
const routes = [
{
path: '/',
component: Overview,
},
- {
- path: '/edges',
- component: Edges,
- },
- {
- path: '/custom-node',
- component: CustomNode,
- },
- {
- path: '/validation',
- component: Validation,
- },
- {
- path: '/provider',
- component: Provider,
- },
- {
- path: '/stress',
- component: Stress,
- },
- {
- path: '/interaction',
- component: Interaction,
- },
{
path: '/basic',
component: Basic,
},
{
- path: '/empty',
- component: Empty,
- },
- {
- path: '/hidden',
- component: Hidden,
- },
- {
- path: '/edge-types',
- component: EdgeTypes,
+ path: '/default-nodes',
+ component: DefaultNodes,
},
{
path: '/custom-connectionline',
component: CustomConnectionLine,
},
+ {
+ path: '/custom-node',
+ component: CustomNode,
+ },
+ {
+ path: '/draghandle',
+ component: DragHandle,
+ },
+ {
+ path: '/dragndrop',
+ component: DragNDrop,
+ },
+ {
+ path: '/edges',
+ component: Edges,
+ },
+ {
+ path: '/edge-types',
+ component: EdgeTypes,
+ },
+ {
+ path: '/empty',
+ component: Empty,
+ },
{
path: '/floating-edges',
component: FloatingEdges,
},
+ {
+ path: '/hidden',
+ component: Hidden,
+ },
+ {
+ path: '/interaction',
+ component: Interaction,
+ },
+ {
+ path: '/layouting',
+ component: Layouting,
+ },
+ {
+ path: '/multiflows',
+ component: MultiFlows,
+ },
+ {
+ path: '/nested-nodes',
+ component: NestedNodes,
+ },
{
path: '/nodetype-change',
component: NodeTypeChange,
@@ -93,28 +110,40 @@ const routes = [
component: NodeTypesObjectChange,
},
{
- path: '/updatable-edge',
- component: UpdatableEdge,
- },
- {
- path: '/update-node',
- component: UpdateNode,
+ path: '/provider',
+ component: Provider,
},
{
path: '/save-restore',
component: SaveRestore,
},
{
- path: '/drag-and-drop',
- component: DragNDrop,
+ path: '/stress',
+ component: Stress,
},
{
- path: '/layouting',
- component: Layout,
+ path: '/subflow',
+ component: Subflow,
},
{
path: '/switch',
- component: SwitchFlows,
+ component: SwitchFlow,
+ },
+ {
+ path: '/touch-device',
+ component: TouchDevice,
+ },
+ {
+ path: '/undirectional',
+ component: Undirectional,
+ },
+ {
+ path: '/updatable-edge',
+ component: UpdatableEdge,
+ },
+ {
+ path: '/update-node',
+ component: UpdateNode,
},
{
path: '/usezoompanhelper',
@@ -125,46 +154,44 @@ const routes = [
component: UseUpdateNodeInternals,
},
{
- path: '/undirectional',
- component: Undirectional,
+ path: '/validation',
+ component: Validation,
},
{
- path: '/multiflows',
- component: MultiFlows,
- },
- {
- path: '/draghandle',
- component: DragHandle,
+ path: '/controlled-uncontrolled',
+ component: ControlledUncontrolled,
},
];
-const Header = withRouter(({ history, location }) => {
- const onChange = (event: ChangeEvent) => history.push(event.target.value);
+const Header = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const onChange = (event: ChangeEvent) => navigate(event.target.value);
return (
-
-
- React Flow Dev
-
-
- {routes.map((route) => (
-
- {route.path === '/' ? 'overview' : route.path.substr(1, route.path.length)}
-
- ))}
-
-
+
+
+ React Flow Dev
+
+
+ {routes.map((route) => (
+
+ {route.path === '/' ? 'overview' : route.path.substring(1, route.path.length)}
+
+ ))}
+
+
);
-});
+};
ReactDOM.render(
-
+
-
+
{routes.map((route) => (
- } key={route.path} />
+ }/>
))}
-
- ,
+
+ ,
document.getElementById('root')
);
diff --git a/example/tsconfig.json b/example/tsconfig.json
index a273b0cf..edc77511 100644
--- a/example/tsconfig.json
+++ b/example/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@@ -20,7 +16,6 @@
"noEmit": true,
"jsx": "react-jsx"
},
- "include": [
- "src"
- ]
+ "include": ["src"],
+ "exclude": ["src_oldapi"]
}
diff --git a/nocss/package.json b/nocss/package.json
index 719fc50b..237eeb31 100644
--- a/nocss/package.json
+++ b/nocss/package.json
@@ -1,6 +1,7 @@
{
"internal": true,
- "main": "../dist/nocss/ReactFlow-nocss.js",
- "module": "../dist/nocss/ReactFlow-nocss.esm.js",
+ "type": "module",
+ "main": "../dist/nocss/index.js",
+ "module": "../dist/nocss/index.js",
"types": "../dist/nocss/index.d.ts"
}
diff --git a/package-lock.json b/package-lock.json
index aae08750..190a0a5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,56 +1,53 @@
{
"name": "react-flow-renderer",
- "version": "9.7.4",
+ "version": "10.0.0-next.51",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "react-flow-renderer",
- "version": "9.7.4",
+ "version": "10.0.0-next.51",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.16.7",
+ "@babel/runtime": "^7.17.2",
"classcat": "^5.0.3",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
- "fast-deep-equal": "^3.1.3",
"react-draggable": "^4.4.4",
- "react-redux": "^7.2.6",
- "redux": "^4.1.2"
+ "zustand": "^3.7.1"
},
"devDependencies": {
- "@babel/core": "^7.16.12",
+ "@babel/core": "^7.17.5",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
- "@rollup/plugin-babel": "^5.3.0",
- "@rollup/plugin-commonjs": "^21.0.1",
+ "@babel/preset-typescript": "^7.16.7",
+ "@rollup/plugin-babel": "^5.3.1",
+ "@rollup/plugin-commonjs": "^21.0.2",
"@rollup/plugin-node-resolve": "^13.1.3",
- "@rollup/plugin-replace": "^3.0.1",
+ "@rollup/plugin-replace": "^4.0.0",
"@svgr/rollup": "^6.2.1",
"@types/d3": "^7.1.0",
- "@types/react-redux": "^7.1.22",
- "@types/redux": "^3.6.31",
- "@types/resize-observer-browser": "^0.1.6",
+ "@types/react": "^17.0.40",
+ "@types/resize-observer-browser": "^0.1.7",
"autoprefixer": "^10.4.2",
"babel-preset-react-app": "^10.0.1",
- "cypress": "^9.4.1",
- "postcss": "^8.4.5",
+ "cypress": "^9.5.1",
+ "postcss": "^8.4.8",
"postcss-cli": "^9.1.0",
"postcss-nested": "^5.0.6",
"prettier": "2.5.1",
- "prop-types": "^15.8.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "release-it": "^14.12.4",
- "rollup": "^2.66.1",
- "rollup-plugin-bundle-size": "^1.0.3",
+ "release-it": "^14.12.5",
+ "rollup": "^2.70.1",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-serve": "^1.1.0",
- "rollup-plugin-typescript2": "^0.31.1",
+ "rollup-plugin-typescript2": "^0.31.2",
+ "rollup-plugin-visualizer": "^5.6.0",
"start-server-and-test": "^1.14.0",
- "typescript": "^4.5.4"
+ "typescript": "^4.6.2"
},
"engines": {
"node": ">=12"
@@ -60,6 +57,18 @@
"react-dom": "16 || 17"
}
},
+ "node_modules/@ampproject/remapping": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
+ "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@@ -82,26 +91,26 @@
}
},
"node_modules/@babel/core": {
- "version": "7.16.12",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz",
- "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==",
+ "version": "7.17.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
+ "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
"dev": true,
"dependencies": {
+ "@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
- "@babel/generator": "^7.16.8",
+ "@babel/generator": "^7.17.3",
"@babel/helper-compilation-targets": "^7.16.7",
"@babel/helper-module-transforms": "^7.16.7",
- "@babel/helpers": "^7.16.7",
- "@babel/parser": "^7.16.12",
+ "@babel/helpers": "^7.17.2",
+ "@babel/parser": "^7.17.3",
"@babel/template": "^7.16.7",
- "@babel/traverse": "^7.16.10",
- "@babel/types": "^7.16.8",
+ "@babel/traverse": "^7.17.3",
+ "@babel/types": "^7.17.0",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.1.2",
- "semver": "^6.3.0",
- "source-map": "^0.5.0"
+ "semver": "^6.3.0"
},
"engines": {
"node": ">=6.9.0"
@@ -112,12 +121,12 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.16.8",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
- "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
+ "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.16.8",
+ "@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
},
@@ -450,14 +459,14 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz",
- "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==",
+ "version": "7.17.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz",
+ "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==",
"dev": true,
"dependencies": {
"@babel/template": "^7.16.7",
- "@babel/traverse": "^7.16.7",
- "@babel/types": "^7.16.7"
+ "@babel/traverse": "^7.17.0",
+ "@babel/types": "^7.17.0"
},
"engines": {
"node": ">=6.9.0"
@@ -478,9 +487,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.16.12",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
- "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==",
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz",
+ "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -1009,12 +1018,12 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz",
- "integrity": "sha512-Xv6mEXqVdaqCBfJFyeab0fH2DnUoMsDmhamxsSi4j8nLd4Vtw213WMJr55xxqipC/YVWyPY3K0blJncPYji+dQ==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz",
+ "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
+ "@babel/helper-plugin-utils": "^7.16.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1615,14 +1624,14 @@
}
},
"node_modules/@babel/plugin-transform-typescript": {
- "version": "7.16.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz",
- "integrity": "sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==",
+ "version": "7.16.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz",
+ "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==",
"dev": true,
"dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.16.0",
- "@babel/helper-plugin-utils": "^7.14.5",
- "@babel/plugin-syntax-typescript": "^7.16.0"
+ "@babel/helper-create-class-features-plugin": "^7.16.7",
+ "@babel/helper-plugin-utils": "^7.16.7",
+ "@babel/plugin-syntax-typescript": "^7.16.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1787,14 +1796,14 @@
}
},
"node_modules/@babel/preset-typescript": {
- "version": "7.16.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz",
- "integrity": "sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz",
+ "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.16.5",
- "@babel/helper-validator-option": "^7.14.5",
- "@babel/plugin-transform-typescript": "^7.16.1"
+ "@babel/helper-plugin-utils": "^7.16.7",
+ "@babel/helper-validator-option": "^7.16.7",
+ "@babel/plugin-transform-typescript": "^7.16.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1804,9 +1813,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
- "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
+ "version": "7.17.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
+ "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
@@ -1829,19 +1838,19 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.16.10",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz",
- "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==",
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
+ "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.16.7",
- "@babel/generator": "^7.16.8",
+ "@babel/generator": "^7.17.3",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
- "@babel/parser": "^7.16.10",
- "@babel/types": "^7.16.8",
+ "@babel/parser": "^7.17.3",
+ "@babel/types": "^7.17.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -1850,9 +1859,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.16.8",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz",
- "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==",
+ "version": "7.17.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
+ "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.16.7",
@@ -1931,6 +1940,31 @@
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
"dev": true
},
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
+ "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
+ "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
+ "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2099,9 +2133,9 @@
}
},
"node_modules/@rollup/plugin-babel": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
- "integrity": "sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
+ "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==",
"dev": true,
"dependencies": {
"@babel/helper-module-imports": "^7.10.4",
@@ -2122,9 +2156,9 @@
}
},
"node_modules/@rollup/plugin-commonjs": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz",
- "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==",
+ "version": "21.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz",
+ "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^3.1.0",
@@ -2163,9 +2197,9 @@
}
},
"node_modules/@rollup/plugin-replace": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.0.1.tgz",
- "integrity": "sha512-989J5oRzf3mm0pO/0djTijdfEh9U3n63BIXN5X7T4U9BP+fN4oxQ6DvDuBvFaHA6scaHQRclqmKQEkBhB7k7Hg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz",
+ "integrity": "sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^3.1.0",
@@ -2520,16 +2554,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/@ts-type/package-dts": {
- "version": "1.0.53",
- "resolved": "https://registry.npmjs.org/@ts-type/package-dts/-/package-dts-1.0.53.tgz",
- "integrity": "sha512-P8bMcjqaUsaBbXOCxBtzT4/pvhQSHTMc91WrpErVfB52zpNRVw2evOYWMe6q3c087+wpvITB7HtppYDnQe1RwA==",
- "dev": true,
- "dependencies": {
- "@types/semver": "^7.3.9",
- "ts-type": "^1.2.40"
- }
- },
"node_modules/@types/cacheable-request": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",
@@ -2807,15 +2831,6 @@
"integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==",
"dev": true
},
- "node_modules/@types/hoist-non-react-statics": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
- "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
- "dependencies": {
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0"
- }
- },
"node_modules/@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
@@ -2846,39 +2861,24 @@
"node_modules/@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
- "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
+ "dev": true
},
"node_modules/@types/react": {
- "version": "17.0.11",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz",
- "integrity": "sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==",
+ "version": "17.0.40",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz",
+ "integrity": "sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==",
+ "dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
- "node_modules/@types/react-redux": {
- "version": "7.1.22",
- "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.22.tgz",
- "integrity": "sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==",
- "dependencies": {
- "@types/hoist-non-react-statics": "^3.3.0",
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0",
- "redux": "^4.0.0"
- }
- },
- "node_modules/@types/redux": {
- "version": "3.6.31",
- "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.31.tgz",
- "integrity": "sha1-QOr6dXXbNrkSzgBZuF3pjCBbBwg=",
- "dev": true
- },
"node_modules/@types/resize-observer-browser": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz",
- "integrity": "sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg==",
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz",
+ "integrity": "sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==",
"dev": true
},
"node_modules/@types/resolve": {
@@ -2902,12 +2902,7 @@
"node_modules/@types/scheduler": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
- "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
- },
- "node_modules/@types/semver": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
- "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==",
+ "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==",
"dev": true
},
"node_modules/@types/sinonjs__fake-timers": {
@@ -2933,27 +2928,14 @@
}
},
"node_modules/@yarn-tool/resolve-package": {
- "version": "1.0.37",
- "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.37.tgz",
- "integrity": "sha512-TnjsNfRmKbg/nWxULBrEkSE6+hmKAURRObfC92n4zZPxrJaDsuTjFtblkTz2ck3VPCcv8UC/Fp6HOGDjm3YRpg==",
+ "version": "1.0.45",
+ "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.45.tgz",
+ "integrity": "sha512-xnfY8JceApkSTliZtr7X6yl1wZYhGbRp0beBMi1OtmvTVTm/ZSt3881Fw1M3ZwhHqr7OEfl8828LJK2q62BvoQ==",
"dev": true,
"dependencies": {
- "@ts-type/package-dts": "^1.0.51",
"pkg-dir": "< 6 >= 5",
"tslib": "^2.3.1",
- "upath2": "^3.1.10"
- }
- },
- "node_modules/@yarn-tool/resolve-package/node_modules/pkg-dir": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
- "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
- "dev": true,
- "dependencies": {
- "find-up": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
+ "upath2": "^3.1.12"
}
},
"node_modules/@yarn-tool/resolve-package/node_modules/tslib": {
@@ -4210,12 +4192,13 @@
"node_modules/csstype": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
- "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
+ "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==",
+ "dev": true
},
"node_modules/cypress": {
- "version": "9.4.1",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.4.1.tgz",
- "integrity": "sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ==",
+ "version": "9.5.1",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.5.1.tgz",
+ "integrity": "sha512-H7lUWB3Svr44gz1rNnj941xmdsCljXoJa2cDneAltjI9leKLMQLm30x6jLlpQ730tiVtIbW5HdUmBzPzwzfUQg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -4593,6 +4576,15 @@
"node": ">=10"
}
},
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -4955,11 +4947,6 @@
"node >=0.6.0"
]
},
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- },
"node_modules/fast-glob": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
@@ -4994,19 +4981,6 @@
"pend": "~1.2.0"
}
},
- "node_modules/figures": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
- "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
- "dev": true,
- "dependencies": {
- "escape-string-regexp": "^1.0.5",
- "object-assign": "^4.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -5029,9 +5003,9 @@
}
},
"node_modules/find-cache-dir": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
- "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
"dev": true,
"dependencies": {
"commondir": "^1.0.1",
@@ -5045,6 +5019,70 @@
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
+ "node_modules/find-cache-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-cache-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-cache-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-cache-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-cache-dir/node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -5376,18 +5414,6 @@
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
"dev": true
},
- "node_modules/gzip-size": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz",
- "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=",
- "dev": true,
- "dependencies": {
- "duplexer": "^0.1.1"
- },
- "engines": {
- "node": ">=0.12.0"
- }
- },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -5400,27 +5426,6 @@
"node": ">= 0.4.0"
}
},
- "node_modules/has-ansi": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
- "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-ansi/node_modules/ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -5451,14 +5456,6 @@
"node": ">=8"
}
},
- "node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
"node_modules/http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@@ -6586,88 +6583,6 @@
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
"dev": true
},
- "node_modules/maxmin": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz",
- "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=",
- "dev": true,
- "dependencies": {
- "chalk": "^1.0.0",
- "figures": "^1.0.1",
- "gzip-size": "^3.0.0",
- "pretty-bytes": "^3.0.0"
- },
- "engines": {
- "node": ">=0.12"
- }
- },
- "node_modules/maxmin/node_modules/ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/maxmin/node_modules/ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/maxmin/node_modules/chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/maxmin/node_modules/pretty-bytes": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz",
- "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=",
- "dev": true,
- "dependencies": {
- "number-is-nan": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/maxmin/node_modules/strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/maxmin/node_modules/supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true,
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -6784,9 +6699,9 @@
"dev": true
},
"node_modules/nanoid": {
- "version": "3.1.30",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
- "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
+ "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
@@ -6896,15 +6811,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
- "node_modules/number-is-nan": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -7482,9 +7388,9 @@
}
},
"node_modules/path-is-network-drive": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.10.tgz",
- "integrity": "sha512-D6kJYPUSKGZBpTM2nv10sOWNdC056p4JDx0y7ARe6gop0aXXm5G86Gn/SyKvaf0Ce8c9Guctf+J+qoFmzuhDQg==",
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz",
+ "integrity": "sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A==",
"dev": true,
"dependencies": {
"tslib": "^2.3.1"
@@ -7512,9 +7418,9 @@
"dev": true
},
"node_modules/path-strip-sep": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.7.tgz",
- "integrity": "sha512-9xDVZPblHde4lTuTDnwqBKr9LTbPZW+Iae63ho500+BpEiZe3X6wvLInHgbB6FSMtwCTvztljw3k2zcNDNESzg==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz",
+ "integrity": "sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA==",
"dev": true,
"dependencies": {
"tslib": "^2.3.1"
@@ -7584,78 +7490,26 @@
}
},
"node_modules/pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
- "dev": true,
- "dependencies": {
- "find-up": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/locate-path": {
"version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
+ "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
"dev": true,
"dependencies": {
- "p-locate": "^4.1.0"
+ "find-up": "^5.0.0"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-dir/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
+ "node": ">=10"
}
},
"node_modules/postcss": {
- "version": "8.4.5",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
- "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
+ "version": "8.4.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz",
+ "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==",
"dev": true,
"dependencies": {
- "nanoid": "^3.1.30",
+ "nanoid": "^3.3.1",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.1"
+ "source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -8584,35 +8438,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
- "node_modules/react-redux": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
- "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
- "dependencies": {
- "@babel/runtime": "^7.15.4",
- "@types/react-redux": "^7.1.20",
- "hoist-non-react-statics": "^3.3.2",
- "loose-envify": "^1.4.0",
- "prop-types": "^15.7.2",
- "react-is": "^17.0.2"
- },
- "peerDependencies": {
- "react": "^16.8.3 || ^17"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- },
- "react-native": {
- "optional": true
- }
- }
- },
- "node_modules/react-redux/node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
- },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -8660,14 +8485,6 @@
"node": ">= 0.10"
}
},
- "node_modules/redux": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
- "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
- "dependencies": {
- "@babel/runtime": "^7.9.2"
- }
- },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -8769,9 +8586,9 @@
}
},
"node_modules/release-it": {
- "version": "14.12.4",
- "resolved": "https://registry.npmjs.org/release-it/-/release-it-14.12.4.tgz",
- "integrity": "sha512-lqf9PMsj7ycCqFHGag8Uv7cE1hNsKa+yKUMe+Fkh9fdOfxu2F01On+YUefRCP0DuQthmr/WyLCYdrjThMEkWFQ==",
+ "version": "14.12.5",
+ "resolved": "https://registry.npmjs.org/release-it/-/release-it-14.12.5.tgz",
+ "integrity": "sha512-mGFbbX8eEKMOhfjq5mZAgGppT8CME1T+vj6xA5tAPYSCuPpcNEDjqiG1tJfPy/XImZI4uS3U6pNP5KyDpy9etg==",
"dev": true,
"dependencies": {
"@iarna/toml": "2.2.5",
@@ -9073,9 +8890,9 @@
}
},
"node_modules/rollup": {
- "version": "2.66.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.1.tgz",
- "integrity": "sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==",
+ "version": "2.70.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz",
+ "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -9087,71 +8904,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/rollup-plugin-bundle-size": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/rollup-plugin-bundle-size/-/rollup-plugin-bundle-size-1.0.3.tgz",
- "integrity": "sha512-aWj0Pvzq90fqbI5vN1IvUrlf4utOqy+AERYxwWjegH1G8PzheMnrRIgQ5tkwKVtQMDP0bHZEACW/zLDF+XgfXQ==",
- "dev": true,
- "dependencies": {
- "chalk": "^1.1.3",
- "maxmin": "^2.1.0"
- }
- },
- "node_modules/rollup-plugin-bundle-size/node_modules/ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup-plugin-bundle-size/node_modules/ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup-plugin-bundle-size/node_modules/chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup-plugin-bundle-size/node_modules/strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup-plugin-bundle-size/node_modules/supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true,
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/rollup-plugin-livereload": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.5.tgz",
@@ -9284,17 +9036,17 @@
}
},
"node_modules/rollup-plugin-typescript2": {
- "version": "0.31.1",
- "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.1.tgz",
- "integrity": "sha512-sklqXuQwQX+stKi4kDfEkneVESPi3YM/2S899vfRdF9Yi40vcC50Oq4A4cSZJNXsAQE/UsBZl5fAOsBLziKmjw==",
+ "version": "0.31.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz",
+ "integrity": "sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q==",
"dev": true,
"dependencies": {
- "@rollup/pluginutils": "^4.1.0",
- "@yarn-tool/resolve-package": "^1.0.36",
- "find-cache-dir": "^3.3.1",
- "fs-extra": "8.1.0",
- "resolve": "1.20.0",
- "tslib": "2.2.0"
+ "@rollup/pluginutils": "^4.1.2",
+ "@yarn-tool/resolve-package": "^1.0.40",
+ "find-cache-dir": "^3.3.2",
+ "fs-extra": "^10.0.0",
+ "resolve": "^1.20.0",
+ "tslib": "^2.3.1"
},
"peerDependencies": {
"rollup": ">=1.26.3",
@@ -9302,9 +9054,9 @@
}
},
"node_modules/rollup-plugin-typescript2/node_modules/@rollup/pluginutils": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
- "integrity": "sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.0.tgz",
+ "integrity": "sha512-2WUyJNRkyH5p487pGnn4tWAsxhEFKN/pT8CMgHshd5H+IXkOnKvKZwsz5ZWz+YCXkleZRAU5kwbfgF8CPfDRqA==",
"dev": true,
"dependencies": {
"estree-walker": "^2.0.1",
@@ -9312,47 +9064,73 @@
},
"engines": {
"node": ">= 8.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0"
}
},
"node_modules/rollup-plugin-typescript2/node_modules/fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz",
+ "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
},
"engines": {
- "node": ">=6 <7 || >=8"
- }
- },
- "node_modules/rollup-plugin-typescript2/node_modules/jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
- "dev": true,
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
+ "node": ">=12"
}
},
"node_modules/rollup-plugin-typescript2/node_modules/tslib": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
- "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
},
- "node_modules/rollup-plugin-typescript2/node_modules/universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "node_modules/rollup-plugin-visualizer": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.6.0.tgz",
+ "integrity": "sha512-CKcc8GTUZjC+LsMytU8ocRr/cGZIfMR7+mdy4YnlyetlmIl/dM8BMnOEpD4JPIGt+ZVW7Db9ZtSsbgyeBH3uTA==",
+ "dev": true,
+ "dependencies": {
+ "nanoid": "^3.1.32",
+ "open": "^8.4.0",
+ "source-map": "^0.7.3",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "rollup-plugin-visualizer": "dist/bin/cli.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "rollup": "^2.0.0"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/open": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
+ "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
+ "dev": true,
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
- "node": ">= 4.0.0"
+ "node": ">= 8"
}
},
"node_modules/rollup-pluginutils": {
@@ -9593,9 +9371,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
- "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -9644,11 +9422,6 @@
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
- "bin": {
- "sshpk-conv": "bin/sshpk-conv",
- "sshpk-sign": "bin/sshpk-sign",
- "sshpk-verify": "bin/sshpk-verify"
- },
"engines": {
"node": ">=0.10.0"
}
@@ -9780,26 +9553,26 @@
"dev": true
},
"node_modules/string-width": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
- "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.0"
+ "strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
- "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
- "ansi-regex": "^5.0.0"
+ "ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
@@ -9972,27 +9745,6 @@
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true
},
- "node_modules/ts-type": {
- "version": "1.2.40",
- "resolved": "https://registry.npmjs.org/ts-type/-/ts-type-1.2.40.tgz",
- "integrity": "sha512-Ux5e7Frys5pX7w8gYchEd0KfhG1H5puXOQC8yOBgMJ1aEwDSypeHLwxIUpKjecni1IGwtLI3JrxCVZaQO9JKmw==",
- "dev": true,
- "dependencies": {
- "tslib": "^2.3.0",
- "typedarray-dts": "^1.0.0"
- },
- "peerDependencies": {
- "@types/bluebird": "*",
- "@types/node": "*",
- "ts-toolbelt": "^8.0.7"
- }
- },
- "node_modules/ts-type/node_modules/tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
- "dev": true
- },
"node_modules/tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
@@ -10029,12 +9781,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/typedarray-dts": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/typedarray-dts/-/typedarray-dts-1.0.0.tgz",
- "integrity": "sha512-Ka0DBegjuV9IPYFT1h0Qqk5U4pccebNIJCGl8C5uU7xtOs+jpJvKGAY4fHGK25hTmXZOEUl9Cnsg5cS6K/b5DA==",
- "dev": true
- },
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
@@ -10045,9 +9791,9 @@
}
},
"node_modules/typescript": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
- "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
+ "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -10140,14 +9886,13 @@
}
},
"node_modules/upath2": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.10.tgz",
- "integrity": "sha512-7ph3GzTaVbQX+oIoMsGtM+9BAWQr+6Mn28TJKPu28+yGpZ+J4am590CPDBlDG0zyuo9T9T7o21ciqNzwIp/q0A==",
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.12.tgz",
+ "integrity": "sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw==",
"dev": true,
"dependencies": {
- "lodash": "^4.17.21",
- "path-is-network-drive": "^1.0.10",
- "path-strip-sep": "^1.0.7",
+ "path-is-network-drive": "^1.0.13",
+ "path-strip-sep": "^1.0.10",
"tslib": "^2.3.1"
},
"peerDependencies": {
@@ -10566,18 +10311,18 @@
}
},
"node_modules/yargs": {
- "version": "17.2.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz",
- "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==",
+ "version": "17.3.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz",
+ "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==",
"dev": true,
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
+ "yargs-parser": "^21.0.0"
},
"engines": {
"node": ">=12"
@@ -10592,6 +10337,15 @@
"node": ">=10"
}
},
+ "node_modules/yargs/node_modules/yargs-parser": {
+ "version": "21.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz",
+ "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
@@ -10613,9 +10367,34 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.1.tgz",
+ "integrity": "sha512-wHBCZlKj+bg03/hP+Tzv24YhnqqP8MCeN9ECPDXoF01062SIbnfl3j9O0znkDw1lNTY0a8WN3F///a0UhhaEqg==",
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
}
},
"dependencies": {
+ "@ampproject/remapping": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
+ "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.0"
+ }
+ },
"@babel/code-frame": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@@ -10632,35 +10411,35 @@
"dev": true
},
"@babel/core": {
- "version": "7.16.12",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz",
- "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==",
+ "version": "7.17.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
+ "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
"dev": true,
"requires": {
+ "@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
- "@babel/generator": "^7.16.8",
+ "@babel/generator": "^7.17.3",
"@babel/helper-compilation-targets": "^7.16.7",
"@babel/helper-module-transforms": "^7.16.7",
- "@babel/helpers": "^7.16.7",
- "@babel/parser": "^7.16.12",
+ "@babel/helpers": "^7.17.2",
+ "@babel/parser": "^7.17.3",
"@babel/template": "^7.16.7",
- "@babel/traverse": "^7.16.10",
- "@babel/types": "^7.16.8",
+ "@babel/traverse": "^7.17.3",
+ "@babel/types": "^7.17.0",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.1.2",
- "semver": "^6.3.0",
- "source-map": "^0.5.0"
+ "semver": "^6.3.0"
}
},
"@babel/generator": {
- "version": "7.16.8",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
- "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
+ "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
"dev": true,
"requires": {
- "@babel/types": "^7.16.8",
+ "@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
}
@@ -10909,14 +10688,14 @@
}
},
"@babel/helpers": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz",
- "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==",
+ "version": "7.17.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz",
+ "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==",
"dev": true,
"requires": {
"@babel/template": "^7.16.7",
- "@babel/traverse": "^7.16.7",
- "@babel/types": "^7.16.7"
+ "@babel/traverse": "^7.17.0",
+ "@babel/types": "^7.17.0"
}
},
"@babel/highlight": {
@@ -10931,9 +10710,9 @@
}
},
"@babel/parser": {
- "version": "7.16.12",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
- "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==",
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz",
+ "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==",
"dev": true
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
@@ -11279,12 +11058,12 @@
}
},
"@babel/plugin-syntax-typescript": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz",
- "integrity": "sha512-Xv6mEXqVdaqCBfJFyeab0fH2DnUoMsDmhamxsSi4j8nLd4Vtw213WMJr55xxqipC/YVWyPY3K0blJncPYji+dQ==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz",
+ "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.14.5"
+ "@babel/helper-plugin-utils": "^7.16.7"
}
},
"@babel/plugin-transform-arrow-functions": {
@@ -11657,14 +11436,14 @@
}
},
"@babel/plugin-transform-typescript": {
- "version": "7.16.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz",
- "integrity": "sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==",
+ "version": "7.16.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz",
+ "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==",
"dev": true,
"requires": {
- "@babel/helper-create-class-features-plugin": "^7.16.0",
- "@babel/helper-plugin-utils": "^7.14.5",
- "@babel/plugin-syntax-typescript": "^7.16.0"
+ "@babel/helper-create-class-features-plugin": "^7.16.7",
+ "@babel/helper-plugin-utils": "^7.16.7",
+ "@babel/plugin-syntax-typescript": "^7.16.7"
}
},
"@babel/plugin-transform-unicode-escapes": {
@@ -11796,20 +11575,20 @@
}
},
"@babel/preset-typescript": {
- "version": "7.16.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz",
- "integrity": "sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz",
+ "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.16.5",
- "@babel/helper-validator-option": "^7.14.5",
- "@babel/plugin-transform-typescript": "^7.16.1"
+ "@babel/helper-plugin-utils": "^7.16.7",
+ "@babel/helper-validator-option": "^7.16.7",
+ "@babel/plugin-transform-typescript": "^7.16.7"
}
},
"@babel/runtime": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
- "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
+ "version": "7.17.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
+ "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -11826,27 +11605,27 @@
}
},
"@babel/traverse": {
- "version": "7.16.10",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz",
- "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==",
+ "version": "7.17.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
+ "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
- "@babel/generator": "^7.16.8",
+ "@babel/generator": "^7.17.3",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
- "@babel/parser": "^7.16.10",
- "@babel/types": "^7.16.8",
+ "@babel/parser": "^7.17.3",
+ "@babel/types": "^7.17.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
- "version": "7.16.8",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz",
- "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==",
+ "version": "7.17.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
+ "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
@@ -11921,6 +11700,28 @@
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
"dev": true
},
+ "@jridgewell/resolve-uri": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
+ "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
+ "dev": true
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
+ "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
+ "dev": true
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
+ "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -12071,9 +11872,9 @@
}
},
"@rollup/plugin-babel": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
- "integrity": "sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
+ "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.10.4",
@@ -12081,9 +11882,9 @@
}
},
"@rollup/plugin-commonjs": {
- "version": "21.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz",
- "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==",
+ "version": "21.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz",
+ "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
@@ -12110,9 +11911,9 @@
}
},
"@rollup/plugin-replace": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.0.1.tgz",
- "integrity": "sha512-989J5oRzf3mm0pO/0djTijdfEh9U3n63BIXN5X7T4U9BP+fN4oxQ6DvDuBvFaHA6scaHQRclqmKQEkBhB7k7Hg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz",
+ "integrity": "sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
@@ -12313,16 +12114,6 @@
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"dev": true
},
- "@ts-type/package-dts": {
- "version": "1.0.53",
- "resolved": "https://registry.npmjs.org/@ts-type/package-dts/-/package-dts-1.0.53.tgz",
- "integrity": "sha512-P8bMcjqaUsaBbXOCxBtzT4/pvhQSHTMc91WrpErVfB52zpNRVw2evOYWMe6q3c087+wpvITB7HtppYDnQe1RwA==",
- "dev": true,
- "requires": {
- "@types/semver": "^7.3.9",
- "ts-type": "^1.2.40"
- }
- },
"@types/cacheable-request": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",
@@ -12600,15 +12391,6 @@
"integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==",
"dev": true
},
- "@types/hoist-non-react-statics": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
- "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
- "requires": {
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0"
- }
- },
"@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
@@ -12639,39 +12421,24 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
- "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
+ "dev": true
},
"@types/react": {
- "version": "17.0.11",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz",
- "integrity": "sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==",
+ "version": "17.0.40",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz",
+ "integrity": "sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==",
+ "dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
- "@types/react-redux": {
- "version": "7.1.22",
- "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.22.tgz",
- "integrity": "sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==",
- "requires": {
- "@types/hoist-non-react-statics": "^3.3.0",
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0",
- "redux": "^4.0.0"
- }
- },
- "@types/redux": {
- "version": "3.6.31",
- "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.31.tgz",
- "integrity": "sha1-QOr6dXXbNrkSzgBZuF3pjCBbBwg=",
- "dev": true
- },
"@types/resize-observer-browser": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz",
- "integrity": "sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg==",
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz",
+ "integrity": "sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==",
"dev": true
},
"@types/resolve": {
@@ -12695,12 +12462,7 @@
"@types/scheduler": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
- "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
- },
- "@types/semver": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
- "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==",
+ "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==",
"dev": true
},
"@types/sinonjs__fake-timers": {
@@ -12726,26 +12488,16 @@
}
},
"@yarn-tool/resolve-package": {
- "version": "1.0.37",
- "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.37.tgz",
- "integrity": "sha512-TnjsNfRmKbg/nWxULBrEkSE6+hmKAURRObfC92n4zZPxrJaDsuTjFtblkTz2ck3VPCcv8UC/Fp6HOGDjm3YRpg==",
+ "version": "1.0.45",
+ "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.45.tgz",
+ "integrity": "sha512-xnfY8JceApkSTliZtr7X6yl1wZYhGbRp0beBMi1OtmvTVTm/ZSt3881Fw1M3ZwhHqr7OEfl8828LJK2q62BvoQ==",
"dev": true,
"requires": {
- "@ts-type/package-dts": "^1.0.51",
"pkg-dir": "< 6 >= 5",
"tslib": "^2.3.1",
- "upath2": "^3.1.10"
+ "upath2": "^3.1.12"
},
"dependencies": {
- "pkg-dir": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
- "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
- "dev": true,
- "requires": {
- "find-up": "^5.0.0"
- }
- },
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
@@ -13682,12 +13434,13 @@
"csstype": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
- "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
+ "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==",
+ "dev": true
},
"cypress": {
- "version": "9.4.1",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.4.1.tgz",
- "integrity": "sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ==",
+ "version": "9.5.1",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.5.1.tgz",
+ "integrity": "sha512-H7lUWB3Svr44gz1rNnj941xmdsCljXoJa2cDneAltjI9leKLMQLm30x6jLlpQ730tiVtIbW5HdUmBzPzwzfUQg==",
"dev": true,
"requires": {
"@cypress/request": "^2.88.10",
@@ -13960,6 +13713,12 @@
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"dev": true
},
+ "define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true
+ },
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -14244,11 +14003,6 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- },
"fast-glob": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
@@ -14280,16 +14034,6 @@
"pend": "~1.2.0"
}
},
- "figures": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
- "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
- "dev": true,
- "requires": {
- "escape-string-regexp": "^1.0.5",
- "object-assign": "^4.1.0"
- }
- },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -14306,14 +14050,62 @@
"dev": true
},
"find-cache-dir": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
- "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
"dev": true,
"requires": {
"commondir": "^1.0.1",
"make-dir": "^3.0.2",
"pkg-dir": "^4.1.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.0.0"
+ }
+ }
}
},
"find-up": {
@@ -14553,15 +14345,6 @@
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
"dev": true
},
- "gzip-size": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz",
- "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=",
- "dev": true,
- "requires": {
- "duplexer": "^0.1.1"
- }
- },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -14571,23 +14354,6 @@
"function-bind": "^1.1.1"
}
},
- "has-ansi": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
- "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- }
- }
- },
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -14606,14 +14372,6 @@
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
"dev": true
},
- "hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "requires": {
- "react-is": "^16.7.0"
- }
- },
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@@ -15465,69 +15223,6 @@
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
"dev": true
},
- "maxmin": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz",
- "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=",
- "dev": true,
- "requires": {
- "chalk": "^1.0.0",
- "figures": "^1.0.1",
- "gzip-size": "^3.0.0",
- "pretty-bytes": "^3.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "dev": true,
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "pretty-bytes": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz",
- "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true
- }
- }
- },
"mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -15617,9 +15312,9 @@
"dev": true
},
"nanoid": {
- "version": "3.1.30",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
- "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
+ "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
"dev": true
},
"new-github-release-url": {
@@ -15690,12 +15385,6 @@
"boolbase": "^1.0.0"
}
},
- "number-is-nan": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "dev": true
- },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -16127,9 +15816,9 @@
"dev": true
},
"path-is-network-drive": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.10.tgz",
- "integrity": "sha512-D6kJYPUSKGZBpTM2nv10sOWNdC056p4JDx0y7ARe6gop0aXXm5G86Gn/SyKvaf0Ce8c9Guctf+J+qoFmzuhDQg==",
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz",
+ "integrity": "sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A==",
"dev": true,
"requires": {
"tslib": "^2.3.1"
@@ -16156,9 +15845,9 @@
"dev": true
},
"path-strip-sep": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.7.tgz",
- "integrity": "sha512-9xDVZPblHde4lTuTDnwqBKr9LTbPZW+Iae63ho500+BpEiZe3X6wvLInHgbB6FSMtwCTvztljw3k2zcNDNESzg==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz",
+ "integrity": "sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA==",
"dev": true,
"requires": {
"tslib": "^2.3.1"
@@ -16218,62 +15907,23 @@
"dev": true
},
"pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
+ "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
"dev": true,
"requires": {
- "find-up": "^4.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "requires": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "requires": {
- "p-locate": "^4.1.0"
- }
- },
- "p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "requires": {
- "p-limit": "^2.2.0"
- }
- }
+ "find-up": "^5.0.0"
}
},
"postcss": {
- "version": "8.4.5",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
- "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
+ "version": "8.4.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz",
+ "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==",
"dev": true,
"requires": {
- "nanoid": "^3.1.30",
+ "nanoid": "^3.3.1",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.1"
+ "source-map-js": "^1.0.2"
}
},
"postcss-calc": {
@@ -16870,26 +16520,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
- "react-redux": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
- "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
- "requires": {
- "@babel/runtime": "^7.15.4",
- "@types/react-redux": "^7.1.20",
- "hoist-non-react-statics": "^3.3.2",
- "loose-envify": "^1.4.0",
- "prop-types": "^15.7.2",
- "react-is": "^17.0.2"
- },
- "dependencies": {
- "react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
- }
- }
- },
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -16928,14 +16558,6 @@
"resolve": "^1.1.6"
}
},
- "redux": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
- "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
- "requires": {
- "@babel/runtime": "^7.9.2"
- }
- },
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -17021,9 +16643,9 @@
}
},
"release-it": {
- "version": "14.12.4",
- "resolved": "https://registry.npmjs.org/release-it/-/release-it-14.12.4.tgz",
- "integrity": "sha512-lqf9PMsj7ycCqFHGag8Uv7cE1hNsKa+yKUMe+Fkh9fdOfxu2F01On+YUefRCP0DuQthmr/WyLCYdrjThMEkWFQ==",
+ "version": "14.12.5",
+ "resolved": "https://registry.npmjs.org/release-it/-/release-it-14.12.5.tgz",
+ "integrity": "sha512-mGFbbX8eEKMOhfjq5mZAgGppT8CME1T+vj6xA5tAPYSCuPpcNEDjqiG1tJfPy/XImZI4uS3U6pNP5KyDpy9etg==",
"dev": true,
"requires": {
"@iarna/toml": "2.2.5",
@@ -17243,66 +16865,14 @@
}
},
"rollup": {
- "version": "2.66.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.1.tgz",
- "integrity": "sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==",
+ "version": "2.70.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz",
+ "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
}
},
- "rollup-plugin-bundle-size": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/rollup-plugin-bundle-size/-/rollup-plugin-bundle-size-1.0.3.tgz",
- "integrity": "sha512-aWj0Pvzq90fqbI5vN1IvUrlf4utOqy+AERYxwWjegH1G8PzheMnrRIgQ5tkwKVtQMDP0bHZEACW/zLDF+XgfXQ==",
- "dev": true,
- "requires": {
- "chalk": "^1.1.3",
- "maxmin": "^2.1.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "dev": true,
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true
- }
- }
- },
"rollup-plugin-livereload": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.5.tgz",
@@ -17401,23 +16971,23 @@
}
},
"rollup-plugin-typescript2": {
- "version": "0.31.1",
- "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.1.tgz",
- "integrity": "sha512-sklqXuQwQX+stKi4kDfEkneVESPi3YM/2S899vfRdF9Yi40vcC50Oq4A4cSZJNXsAQE/UsBZl5fAOsBLziKmjw==",
+ "version": "0.31.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz",
+ "integrity": "sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q==",
"dev": true,
"requires": {
- "@rollup/pluginutils": "^4.1.0",
- "@yarn-tool/resolve-package": "^1.0.36",
- "find-cache-dir": "^3.3.1",
- "fs-extra": "8.1.0",
- "resolve": "1.20.0",
- "tslib": "2.2.0"
+ "@rollup/pluginutils": "^4.1.2",
+ "@yarn-tool/resolve-package": "^1.0.40",
+ "find-cache-dir": "^3.3.2",
+ "fs-extra": "^10.0.0",
+ "resolve": "^1.20.0",
+ "tslib": "^2.3.1"
},
"dependencies": {
"@rollup/pluginutils": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
- "integrity": "sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.0.tgz",
+ "integrity": "sha512-2WUyJNRkyH5p487pGnn4tWAsxhEFKN/pT8CMgHshd5H+IXkOnKvKZwsz5ZWz+YCXkleZRAU5kwbfgF8CPfDRqA==",
"dev": true,
"requires": {
"estree-walker": "^2.0.1",
@@ -17425,35 +16995,51 @@
}
},
"fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz",
+ "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- }
- },
- "jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.6"
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
}
},
"tslib": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
- "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
+ }
+ }
+ },
+ "rollup-plugin-visualizer": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.6.0.tgz",
+ "integrity": "sha512-CKcc8GTUZjC+LsMytU8ocRr/cGZIfMR7+mdy4YnlyetlmIl/dM8BMnOEpD4JPIGt+ZVW7Db9ZtSsbgyeBH3uTA==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.1.32",
+ "open": "^8.4.0",
+ "source-map": "^0.7.3",
+ "yargs": "^17.3.1"
+ },
+ "dependencies": {
+ "open": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
+ "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
+ "dev": true,
+ "requires": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ }
},
- "universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
@@ -17643,9 +17229,9 @@
"dev": true
},
"source-map-js": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
- "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"sourcemap-codec": {
@@ -17777,23 +17363,23 @@
"dev": true
},
"string-width": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
- "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.0"
+ "strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
- "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
- "ansi-regex": "^5.0.0"
+ "ansi-regex": "^5.0.1"
}
},
"strip-final-newline": {
@@ -17926,24 +17512,6 @@
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true
},
- "ts-type": {
- "version": "1.2.40",
- "resolved": "https://registry.npmjs.org/ts-type/-/ts-type-1.2.40.tgz",
- "integrity": "sha512-Ux5e7Frys5pX7w8gYchEd0KfhG1H5puXOQC8yOBgMJ1aEwDSypeHLwxIUpKjecni1IGwtLI3JrxCVZaQO9JKmw==",
- "dev": true,
- "requires": {
- "tslib": "^2.3.0",
- "typedarray-dts": "^1.0.0"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
- "dev": true
- }
- }
- },
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
@@ -17971,12 +17539,6 @@
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true
},
- "typedarray-dts": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/typedarray-dts/-/typedarray-dts-1.0.0.tgz",
- "integrity": "sha512-Ka0DBegjuV9IPYFT1h0Qqk5U4pccebNIJCGl8C5uU7xtOs+jpJvKGAY4fHGK25hTmXZOEUl9Cnsg5cS6K/b5DA==",
- "dev": true
- },
"typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
@@ -17987,9 +17549,9 @@
}
},
"typescript": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
- "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
+ "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
"dev": true
},
"unicode-canonical-property-names-ecmascript": {
@@ -18054,14 +17616,13 @@
"dev": true
},
"upath2": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.10.tgz",
- "integrity": "sha512-7ph3GzTaVbQX+oIoMsGtM+9BAWQr+6Mn28TJKPu28+yGpZ+J4am590CPDBlDG0zyuo9T9T7o21ciqNzwIp/q0A==",
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.12.tgz",
+ "integrity": "sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw==",
"dev": true,
"requires": {
- "lodash": "^4.17.21",
- "path-is-network-drive": "^1.0.10",
- "path-strip-sep": "^1.0.7",
+ "path-is-network-drive": "^1.0.13",
+ "path-strip-sep": "^1.0.10",
"tslib": "^2.3.1"
},
"dependencies": {
@@ -18376,18 +17937,26 @@
"dev": true
},
"yargs": {
- "version": "17.2.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz",
- "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==",
+ "version": "17.3.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz",
+ "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==",
"dev": true,
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
+ "yargs-parser": "^21.0.0"
+ },
+ "dependencies": {
+ "yargs-parser": {
+ "version": "21.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz",
+ "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==",
+ "dev": true
+ }
}
},
"yargs-parser": {
@@ -18411,6 +17980,11 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
+ },
+ "zustand": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.1.tgz",
+ "integrity": "sha512-wHBCZlKj+bg03/hP+Tzv24YhnqqP8MCeN9ECPDXoF01062SIbnfl3j9O0znkDw1lNTY0a8WN3F///a0UhhaEqg=="
}
}
}
diff --git a/package.json b/package.json
index 8f5eed28..3116942b 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,13 @@
{
"name": "react-flow-renderer",
- "version": "9.7.4",
+ "version": "10.0.0-next.51",
"engines": {
"node": ">=12"
},
- "main": "dist/ReactFlow.js",
- "module": "dist/ReactFlow.esm.js",
- "types": "dist/index.d.ts",
+ "type": "module",
+ "main": "dist/esm/index.js",
+ "module": "dist/esm/index.js",
+ "types": "dist/esm/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
@@ -28,51 +29,48 @@
"cy:open": "cypress open",
"release": "npm run test && release-it",
"release:notest": "release-it",
- "release:next": "release-it --preRelease=next"
+ "release:next": "release-it --preRelease=next --no-git"
},
"dependencies": {
- "@babel/runtime": "^7.16.7",
+ "@babel/runtime": "^7.17.2",
"classcat": "^5.0.3",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
- "fast-deep-equal": "^3.1.3",
"react-draggable": "^4.4.4",
- "react-redux": "^7.2.6",
- "redux": "^4.1.2"
+ "zustand": "^3.7.1"
},
"devDependencies": {
- "@babel/core": "^7.16.12",
+ "@babel/core": "^7.17.5",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
- "@rollup/plugin-babel": "^5.3.0",
- "@rollup/plugin-commonjs": "^21.0.1",
+ "@babel/preset-typescript": "^7.16.7",
+ "@rollup/plugin-babel": "^5.3.1",
+ "@rollup/plugin-commonjs": "^21.0.2",
"@rollup/plugin-node-resolve": "^13.1.3",
- "@rollup/plugin-replace": "^3.0.1",
+ "@rollup/plugin-replace": "^4.0.0",
"@svgr/rollup": "^6.2.1",
"@types/d3": "^7.1.0",
- "@types/react-redux": "^7.1.22",
- "@types/redux": "^3.6.31",
- "@types/resize-observer-browser": "^0.1.6",
+ "@types/react": "^17.0.40",
+ "@types/resize-observer-browser": "^0.1.7",
"autoprefixer": "^10.4.2",
"babel-preset-react-app": "^10.0.1",
- "cypress": "^9.4.1",
- "postcss": "^8.4.5",
+ "cypress": "^9.5.1",
+ "postcss": "^8.4.8",
"postcss-cli": "^9.1.0",
"postcss-nested": "^5.0.6",
"prettier": "2.5.1",
- "prop-types": "^15.8.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "release-it": "^14.12.4",
- "rollup": "^2.66.1",
- "rollup-plugin-bundle-size": "^1.0.3",
+ "release-it": "^14.12.5",
+ "rollup": "^2.70.1",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-serve": "^1.1.0",
- "rollup-plugin-typescript2": "^0.31.1",
+ "rollup-plugin-typescript2": "^0.31.2",
+ "rollup-plugin-visualizer": "^5.6.0",
"start-server-and-test": "^1.14.0",
- "typescript": "^4.5.4"
+ "typescript": "^4.6.2"
},
"peerDependencies": {
"react": "16 || 17",
diff --git a/postcss.config.js b/postcss.config.js
deleted file mode 100644
index 859b96e5..00000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- plugins: [require('autoprefixer'), require('postcss-nested')],
-};
diff --git a/prettier.config.js b/prettier.config.js
deleted file mode 100644
index a9a2520b..00000000
--- a/prettier.config.js
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = {
- trailingComma: 'es5',
- singleQuote: true,
- printWidth: 120
-};
diff --git a/rollup.config.js b/rollup.config.js
index cc1ab8a1..a1240555 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -2,11 +2,11 @@ import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import postcss from 'rollup-plugin-postcss';
-import bundleSize from 'rollup-plugin-bundle-size';
import replace from '@rollup/plugin-replace';
import svgr from '@svgr/rollup';
import typescript from 'rollup-plugin-typescript2';
import { DEFAULT_EXTENSIONS as DEFAULT_BABEL_EXTENSIONS } from '@babel/core';
+import { visualizer } from 'rollup-plugin-visualizer';
import pkg from './package.json';
@@ -14,38 +14,51 @@ const isProd = process.env.NODE_ENV === 'production';
const isTesting = process.env.NODE_ENV === 'testing';
const processEnv = isProd || isTesting ? 'production' : 'development';
-export const baseConfig = ({ mainFile = pkg.main, moduleFile = pkg.module, injectCSS = true } = {}) => ({
- input: 'src/index.ts',
- external: ['react', 'react-dom', (id) => id.includes('@babel/runtime')],
+export const baseConfig = ({ outputDir = 'dist/esm', injectCSS = true } = {}) => ({
+ input: [
+ 'src/index.ts',
+ 'src/additional-components/Controls/index.tsx',
+ 'src/additional-components/Background/index.tsx',
+ 'src/additional-components/MiniMap/index.tsx',
+ 'src/hooks/useReactFlow.ts',
+ 'src/hooks/useNodes.ts',
+ 'src/hooks/useEdges.ts',
+ 'src/hooks/useViewport.ts',
+ 'src/hooks/useUpdateNodeInternals.ts',
+ ],
+ external: [
+ 'react',
+ 'react-dom',
+ 'classcat',
+ 'd3-selection',
+ 'd3-zoom',
+ 'react-draggable',
+ 'zustand',
+ 'zustand/shallow',
+ 'zustand/context',
+ (id) => id.includes('@babel/runtime'),
+ ],
onwarn(warning, rollupWarn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY') {
rollupWarn(warning);
}
},
- output: [
- {
- file: mainFile,
- format: 'cjs',
- sourcemap: true,
- exports: 'named',
- },
- {
- file: moduleFile,
- format: 'esm',
- sourcemap: true,
- exports: 'named',
- },
- ],
+ output: {
+ dir: outputDir,
+ format: 'esm',
+ sourcemap: true,
+ },
+
plugins: [
replace({
__ENV__: JSON.stringify(processEnv),
__REACT_FLOW_VERSION__: JSON.stringify(pkg.version),
+ __INJECT_STYLES__: injectCSS,
preventAssignment: true,
}),
- bundleSize(),
postcss({
minimize: isProd,
- inject: injectCSS,
+ inject: false,
}),
svgr(),
resolve(),
@@ -60,6 +73,7 @@ export const baseConfig = ({ mainFile = pkg.main, moduleFile = pkg.module, injec
exclude: 'node_modules/**',
babelHelpers: 'runtime',
}),
+ visualizer(),
],
});
@@ -67,8 +81,7 @@ export default isProd && !isTesting
? [
baseConfig(),
baseConfig({
- mainFile: 'dist/nocss/ReactFlow-nocss.js',
- moduleFile: 'dist/nocss/ReactFlow-nocss.esm.js',
+ outputDir: 'dist/nocss',
injectCSS: false,
}),
]
diff --git a/src/additional-components/Background/index.tsx b/src/additional-components/Background/index.tsx
index 0e99a835..89b87e46 100644
--- a/src/additional-components/Background/index.tsx
+++ b/src/additional-components/Background/index.tsx
@@ -1,22 +1,17 @@
-import React, { memo, useMemo, FC, HTMLAttributes } from 'react';
+import React, { memo, useMemo, FC } from 'react';
import cc from 'classcat';
-import { useStoreState } from '../../store/hooks';
-import { BackgroundVariant } from '../../types';
+import { useStore } from '../../store';
import { createGridLinesPath, createGridDotsPath } from './utils';
-
-export interface BackgroundProps extends HTMLAttributes {
- variant?: BackgroundVariant;
- gap?: number;
- color?: string;
- size?: number;
-}
+import { BackgroundVariant, ReactFlowState, BackgroundProps } from '../../types';
const defaultColors = {
[BackgroundVariant.Dots]: '#81818a',
[BackgroundVariant.Lines]: '#eee',
};
+const transformSelector = (s: ReactFlowState) => s.transform;
+
const Background: FC = ({
variant = BackgroundVariant.Dots,
gap = 15,
@@ -25,11 +20,11 @@ const Background: FC = ({
style,
className,
}) => {
- const [x, y, scale] = useStoreState((s) => s.transform);
+ const [x, y, scale] = useStore(transformSelector);
// when there are multiple flows on a page we need to make sure that every background gets its own pattern.
const patternId = useMemo(() => `pattern-${Math.floor(Math.random() * 100000)}`, []);
- const bgClasses = cc(['react-flow__background', className]);
+ const bgClasses = cc(['react-flow__background', 'react-flow__container', className]);
const scaledGap = gap * scale;
const xOffset = x % scaledGap;
const yOffset = y % scaledGap;
diff --git a/src/additional-components/Controls/index.tsx b/src/additional-components/Controls/index.tsx
index 3cb512c2..2e931582 100644
--- a/src/additional-components/Controls/index.tsx
+++ b/src/additional-components/Controls/index.tsx
@@ -1,7 +1,8 @@
-import React, { memo, useCallback, HTMLAttributes, FC, useEffect, useState } from 'react';
+import React, { memo, useCallback, FC, useEffect, useState } from 'react';
import cc from 'classcat';
-import { useStoreState, useStoreActions } from '../../store/hooks';
+import { useStore, useStoreApi } from '../../store';
+import useReactFlow from '../../hooks/useReactFlow';
import PlusIcon from '../../../assets/icons/plus.svg';
import MinusIcon from '../../../assets/icons/minus.svg';
@@ -9,21 +10,7 @@ import FitviewIcon from '../../../assets/icons/fitview.svg';
import LockIcon from '../../../assets/icons/lock.svg';
import UnlockIcon from '../../../assets/icons/unlock.svg';
-import useZoomPanHelper from '../../hooks/useZoomPanHelper';
-import { FitViewParams } from '../../types';
-
-export interface ControlProps extends HTMLAttributes {
- showZoom?: boolean;
- showFitView?: boolean;
- showInteractive?: boolean;
- fitViewParams?: FitViewParams;
- onZoomIn?: () => void;
- onZoomOut?: () => void;
- onFitView?: () => void;
- onInteractiveChange?: (interactiveStatus: boolean) => void;
-}
-
-export interface ControlButtonProps extends HTMLAttributes {}
+import { ControlProps, ControlButtonProps, ReactFlowState } from '../../types';
export const ControlButton: FC = ({ children, className, ...rest }) => (
@@ -31,12 +18,14 @@ export const ControlButton: FC = ({ children, className, ...
);
+const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable;
+
const Controls: FC = ({
style,
showZoom = true,
showFitView = true,
showInteractive = true,
- fitViewParams,
+ fitViewOptions,
onZoomIn,
onZoomOut,
onFitView,
@@ -44,11 +33,11 @@ const Controls: FC = ({
className,
children,
}) => {
+ const store = useStoreApi();
const [isVisible, setIsVisible] = useState(false);
- const setInteractive = useStoreActions((actions) => actions.setInteractive);
- const { zoomIn, zoomOut, fitView } = useZoomPanHelper();
+ const isInteractive = useStore(isInteractiveSelector);
+ const { zoomIn, zoomOut, fitView } = useReactFlow();
- const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable);
const mapClasses = cc(['react-flow__controls', className]);
const onZoomInHandler = useCallback(() => {
@@ -62,14 +51,19 @@ const Controls: FC = ({
}, [zoomOut, onZoomOut]);
const onFitViewHandler = useCallback(() => {
- fitView?.(fitViewParams);
+ fitView?.(fitViewOptions);
onFitView?.();
- }, [fitView, fitViewParams, onFitView]);
+ }, [fitView, fitViewOptions, onFitView]);
const onInteractiveChangeHandler = useCallback(() => {
- setInteractive?.(!isInteractive);
+ store.setState({
+ nodesDraggable: !isInteractive,
+ nodesConnectable: !isInteractive,
+ elementsSelectable: !isInteractive,
+ });
+
onInteractiveChange?.(!isInteractive);
- }, [isInteractive, setInteractive, onInteractiveChange]);
+ }, [isInteractive, onInteractiveChange]);
useEffect(() => {
setIsVisible(true);
@@ -83,21 +77,41 @@ const Controls: FC = ({
{showZoom && (
<>
-
+
-
+
>
)}
{showFitView && (
-
+
)}
{showInteractive && (
-
+
{isInteractive ? : }
)}
diff --git a/src/additional-components/MiniMap/MiniMapNode.tsx b/src/additional-components/MiniMap/MiniMapNode.tsx
index d8a48e9e..e879211b 100644
--- a/src/additional-components/MiniMap/MiniMapNode.tsx
+++ b/src/additional-components/MiniMap/MiniMapNode.tsx
@@ -26,7 +26,7 @@ const MiniMapNode = ({
strokeWidth,
className,
borderRadius,
- shapeRendering
+ shapeRendering,
}: MiniMapNodeProps) => {
const { background, backgroundColor } = style || {};
const fill = (color || background || backgroundColor) as string;
diff --git a/src/additional-components/MiniMap/index.tsx b/src/additional-components/MiniMap/index.tsx
index dbb86f6c..7b579d28 100644
--- a/src/additional-components/MiniMap/index.tsx
+++ b/src/additional-components/MiniMap/index.tsx
@@ -1,27 +1,27 @@
-import React, { memo, HTMLAttributes } from 'react';
+import React, { memo } from 'react';
import cc from 'classcat';
+import shallow from 'zustand/shallow';
-import { useStoreState } from '../../store/hooks';
-import { getRectOfNodes, getBoundsofRects } from '../../utils/graph';
-import { Node, Rect } from '../../types';
import MiniMapNode from './MiniMapNode';
-type StringFunc = (node: Node) => string;
+import { useStore } from '../../store';
+import { getRectOfNodes } from '../../utils/graph';
+import { getBoundsofRects } from '../../utils';
-export interface MiniMapProps extends HTMLAttributes {
- nodeColor?: string | StringFunc;
- nodeStrokeColor?: string | StringFunc;
- nodeClassName?: string | StringFunc;
- nodeBorderRadius?: number;
- nodeStrokeWidth?: number;
- maskColor?: string;
-}
+import { MiniMapProps, GetMiniMapNodeAttribute, ReactFlowState, Rect } from '../../types';
declare const window: any;
const defaultWidth = 200;
const defaultHeight = 150;
+const selector = (s: ReactFlowState) => ({
+ width: s.width,
+ height: s.height,
+ transform: s.transform,
+ nodeInternals: s.nodeInternals,
+});
+
const MiniMap = ({
style,
className,
@@ -32,20 +32,22 @@ const MiniMap = ({
nodeStrokeWidth = 2,
maskColor = 'rgb(240, 242, 243, 0.7)',
}: MiniMapProps) => {
- const containerWidth = useStoreState((s) => s.width);
- const containerHeight = useStoreState((s) => s.height);
- const [tX, tY, tScale] = useStoreState((s) => s.transform);
- const nodes = useStoreState((s) => s.nodes);
+ const { width: containerWidth, height: containerHeight, transform, nodeInternals } = useStore(selector, shallow);
+ const [tX, tY, tScale] = transform;
const mapClasses = cc(['react-flow__minimap', className]);
const elementWidth = (style?.width || defaultWidth)! as number;
const elementHeight = (style?.height || defaultHeight)! as number;
- const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
- const nodeStrokeColorFunc = (nodeStrokeColor instanceof Function
- ? nodeStrokeColor
- : () => nodeStrokeColor) as StringFunc;
- const nodeClassNameFunc = (nodeClassName instanceof Function ? nodeClassName : () => nodeClassName) as StringFunc;
- const hasNodes = nodes && nodes.length;
+ const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as GetMiniMapNodeAttribute;
+ const nodeStrokeColorFunc = (
+ nodeStrokeColor instanceof Function ? nodeStrokeColor : () => nodeStrokeColor
+ ) as GetMiniMapNodeAttribute;
+ const nodeClassNameFunc = (
+ nodeClassName instanceof Function ? nodeClassName : () => nodeClassName
+ ) as GetMiniMapNodeAttribute;
+ const hasNodes = nodeInternals && nodeInternals.size > 0;
+ // @TODO: work with nodeInternals instead of converting it to an array
+ const nodes = Array.from(nodeInternals).map(([_, node]) => node);
const bb = getRectOfNodes(nodes);
const viewBB: Rect = {
x: -tX / tScale,
@@ -64,7 +66,7 @@ const MiniMap = ({
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
const width = viewWidth + offset * 2;
const height = viewHeight + offset * 2;
- const shapeRendering = (typeof window === "undefined" || !!window.chrome) ? "crispEdges" : "geometricPrecision";
+ const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
return (
- {nodes
- .filter((node) => !node.isHidden)
- .map((node) => (
-
- ))}
+ {Array.from(nodeInternals)
+ .filter(([_, node]) => !node.hidden && node.width && node.height)
+ .map(([_, node]) => {
+ const positionAbsolute = nodeInternals.get(node.id)?.positionAbsolute;
+
+ return (
+
+ );
+ })}
{
- const store = useMemo(() => {
- return configureStore(initialState);
- }, []);
-
- return {children} ;
-};
+const ReactFlowProvider: FC = ({ children }) => {children} ;
ReactFlowProvider.displayName = 'ReactFlowProvider';
diff --git a/src/additional-components/index.ts b/src/additional-components/index.ts
index 3c96dfb6..3f030a79 100644
--- a/src/additional-components/index.ts
+++ b/src/additional-components/index.ts
@@ -1,5 +1,5 @@
// These components are not used by React Flow directly
-// but the user can add them as children of a React Flow component
+// They can be added as children of a React Flow component
export { default as MiniMap } from './MiniMap';
export { default as Controls, ControlButton } from './Controls';
diff --git a/src/components/Attribution/index.tsx b/src/components/Attribution/index.tsx
new file mode 100644
index 00000000..2fcb9df1
--- /dev/null
+++ b/src/components/Attribution/index.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import cc from 'classcat';
+
+import { AttributionPosition, ProOptions } from '../../types';
+
+type AttributionProps = {
+ proOptions?: ProOptions;
+ position?: AttributionPosition;
+};
+
+function Attribution({ proOptions, position = 'bottom-right' }: AttributionProps) {
+ if (
+ (proOptions?.account === 'paid-starter' || proOptions?.account === 'paid-enterprise') &&
+ proOptions?.hideAttribution
+ ) {
+ return null;
+ }
+
+ const positionClasses = `${position}`.split('-');
+
+ return (
+
+ );
+}
+
+export default Attribution;
diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx
index 98b162e5..4a4db773 100644
--- a/src/components/ConnectionLine/index.tsx
+++ b/src/components/ConnectionLine/index.tsx
@@ -1,38 +1,39 @@
-import React, { useEffect, useState, CSSProperties } from 'react';
+import React, { useRef, CSSProperties } from 'react';
+import shallow from 'zustand/shallow';
+import { useStore } from '../../store';
import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import {
- ElementId,
- Node,
- Transform,
HandleElement,
- Position,
ConnectionLineType,
ConnectionLineComponent,
HandleType,
+ Node,
+ ReactFlowState,
+ Position,
} from '../../types';
+import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
interface ConnectionLineProps {
- connectionNodeId: ElementId;
- connectionHandleId: ElementId | null;
+ connectionNodeId: string;
+ connectionHandleId: string | null;
connectionHandleType: HandleType;
connectionPositionX: number;
connectionPositionY: number;
connectionLineType: ConnectionLineType;
- nodes: Node[];
- transform: Transform;
isConnectable: boolean;
connectionLineStyle?: CSSProperties;
CustomConnectionLineComponent?: ConnectionLineComponent;
}
-const getSourceHandle = (handleId: ElementId | null, sourceNode: Node, connectionHandleType: HandleType) => {
- const handleTypeInverted = connectionHandleType === 'source' ? 'target' : 'source';
- const handleBound =
- sourceNode.__rf.handleBounds[connectionHandleType] || sourceNode.__rf.handleBounds[handleTypeInverted];
+const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform });
- return handleId ? handleBound.find((d: HandleElement) => d.id === handleId) : handleBound[0];
+const getSourceHandle = (handleId: string | null, sourceNode: Node, connectionHandleType: HandleType) => {
+ const handleTypeInverted = connectionHandleType === 'source' ? 'target' : 'source';
+ const handleBound = sourceNode.handleBounds?.[connectionHandleType] || sourceNode.handleBounds?.[handleTypeInverted];
+
+ return handleId ? handleBound?.find((d: HandleElement) => d.id === handleId) : handleBound?.[0];
};
export default ({
@@ -43,29 +44,29 @@ export default ({
connectionPositionX,
connectionPositionY,
connectionLineType = ConnectionLineType.Bezier,
- nodes = [],
- transform,
isConnectable,
CustomConnectionLineComponent,
}: ConnectionLineProps) => {
- const [sourceNode, setSourceNode] = useState(null);
const nodeId = connectionNodeId;
const handleId = connectionHandleId;
- useEffect(() => {
- const nextSourceNode = nodes.find((n) => n.id === nodeId) || null;
- setSourceNode(nextSourceNode);
- }, []);
+ const { nodeInternals, transform } = useStore(selector, shallow);
+ const sourceNode = useRef(nodeInternals.get(nodeId));
- if (!sourceNode || !isConnectable) {
+ if (
+ !sourceNode.current ||
+ !sourceNode.current ||
+ !isConnectable ||
+ !sourceNode.current.handleBounds?.[connectionHandleType]
+ ) {
return null;
}
- const sourceHandle = getSourceHandle(handleId, sourceNode, connectionHandleType);
- const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rf.width / 2;
- const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rf.height;
- const sourceX = sourceNode.__rf.position.x + sourceHandleX;
- const sourceY = sourceNode.__rf.position.y + sourceHandleY;
+ const sourceHandle = getSourceHandle(handleId, sourceNode.current, connectionHandleType);
+ const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (sourceNode.current?.width ?? 0) / 2;
+ const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.current?.height ?? 0;
+ const sourceX = (sourceNode.current.positionAbsolute?.x || 0) + sourceHandleX;
+ const sourceY = (sourceNode.current.positionAbsolute?.y || 0) + sourceHandleY;
const targetX = (connectionPositionX - transform[0]) / transform[2];
const targetY = (connectionPositionY - transform[1]) / transform[2];
@@ -85,7 +86,7 @@ export default ({
targetPosition={targetPosition}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
- sourceNode={sourceNode}
+ sourceNode={sourceNode.current as Node}
sourceHandle={sourceHandle}
/>
@@ -94,34 +95,26 @@ export default ({
let dAttr: string = '';
+ const pathParams = {
+ sourceX,
+ sourceY,
+ sourcePosition: sourceHandle?.position,
+ targetX,
+ targetY,
+ targetPosition,
+ };
+
if (connectionLineType === ConnectionLineType.Bezier) {
- dAttr = getBezierPath({
- sourceX,
- sourceY,
- sourcePosition: sourceHandle?.position,
- targetX,
- targetY,
- targetPosition,
- });
+ dAttr = getBezierPath(pathParams);
} else if (connectionLineType === ConnectionLineType.Step) {
dAttr = getSmoothStepPath({
- sourceX,
- sourceY,
- sourcePosition: sourceHandle?.position,
- targetX,
- targetY,
- targetPosition,
+ ...pathParams,
borderRadius: 0,
});
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
- dAttr = getSmoothStepPath({
- sourceX,
- sourceY,
- sourcePosition: sourceHandle?.position,
- targetX,
- targetY,
- targetPosition,
- });
+ dAttr = getSmoothStepPath(pathParams);
+ } else if (connectionLineType === ConnectionLineType.SimpleBezier) {
+ dAttr = getSimpleBezierPath(pathParams);
} else {
dAttr = `M${sourceX},${sourceY} ${targetX},${targetY}`;
}
diff --git a/src/components/Edges/BaseEdge.tsx b/src/components/Edges/BaseEdge.tsx
new file mode 100644
index 00000000..19d0ab37
--- /dev/null
+++ b/src/components/Edges/BaseEdge.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+
+import EdgeText from './EdgeText';
+import { BaseEdgeProps } from '../../types';
+
+export default ({
+ path,
+ centerX,
+ centerY,
+ label,
+ labelStyle,
+ labelShowBg,
+ labelBgStyle,
+ labelBgPadding,
+ labelBgBorderRadius,
+ style,
+ markerEnd,
+ markerStart,
+}: BaseEdgeProps) => {
+ const text = label ? (
+
+ ) : null;
+
+ return (
+ <>
+
+ {text}
+ >
+ );
+};
diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx
index b4c94197..74d87372 100644
--- a/src/components/Edges/BezierEdge.tsx
+++ b/src/components/Edges/BezierEdge.tsx
@@ -1,98 +1,111 @@
-import React, { memo } from 'react';
-
-import EdgeText from './EdgeText';
-
-import { getMarkerEnd, getCenter } from './utils';
-import { EdgeProps, Position } from '../../types';
-
-interface GetBezierPathParams {
- sourceX: number;
- sourceY: number;
- sourcePosition?: Position;
- targetX: number;
- targetY: number;
- targetPosition?: Position;
- centerX?: number;
- centerY?: number;
-}
-
-export function getBezierPath({
- sourceX,
- sourceY,
- sourcePosition = Position.Bottom,
- targetX,
- targetY,
- targetPosition = Position.Top,
- centerX,
- centerY,
-}: GetBezierPathParams): string {
- const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY });
- const leftAndRight = [Position.Left, Position.Right];
-
- const cX = typeof centerX !== 'undefined' ? centerX : _centerX;
- const cY = typeof centerY !== 'undefined' ? centerY : _centerY;
-
- let path = `M${sourceX},${sourceY} C${sourceX},${cY} ${targetX},${cY} ${targetX},${targetY}`;
-
- if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
- path = `M${sourceX},${sourceY} C${cX},${sourceY} ${cX},${targetY} ${targetX},${targetY}`;
- } else if (leftAndRight.includes(targetPosition)) {
- path = `M${sourceX},${sourceY} Q${sourceX},${targetY} ${targetX},${targetY}`;
- } else if (leftAndRight.includes(sourcePosition)) {
- path = `M${sourceX},${sourceY} Q${targetX},${sourceY} ${targetX},${targetY}`;
- }
-
- return path;
-}
-
-export default memo(
- ({
- sourceX,
- sourceY,
- targetX,
- targetY,
- sourcePosition = Position.Bottom,
- targetPosition = Position.Top,
- label,
- labelStyle,
- labelShowBg,
- labelBgStyle,
- labelBgPadding,
- labelBgBorderRadius,
- style,
- arrowHeadType,
- markerEndId,
- }: EdgeProps) => {
- const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition });
- const path = getBezierPath({
- sourceX,
- sourceY,
- sourcePosition,
- targetX,
- targetY,
- targetPosition,
- });
-
- const text = label ? (
-
- ) : null;
-
- const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
-
- return (
- <>
-
- {text}
- >
- );
- }
-);
+import React, { memo } from 'react';
+import { EdgeProps, Position } from '../../types';
+import BaseEdge from './BaseEdge';
+import { getCenter } from './utils';
+
+export interface GetBezierPathParams {
+ sourceX: number;
+ sourceY: number;
+ sourcePosition?: Position;
+ targetX: number;
+ targetY: number;
+ targetPosition?: Position;
+ curvature?: number;
+ centerX?: number;
+ centerY?: number;
+}
+
+export function getBezierPath({
+ sourceX,
+ sourceY,
+ sourcePosition = Position.Bottom,
+ targetX,
+ targetY,
+ targetPosition = Position.Top,
+ curvature = 0.25,
+ centerX,
+ centerY,
+}: GetBezierPathParams): string {
+ const leftAndRight = [Position.Left, Position.Right];
+ const hasCurvature = curvature > 0;
+ let cX,
+ cY = 0;
+
+ const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY });
+
+ if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
+ cX = typeof centerX !== 'undefined' ? centerX : _centerX;
+ const distanceX = targetX - sourceX;
+ const absDistanceX = Math.abs(Math.min(0, distanceX));
+ const amtX = (Math.sqrt(absDistanceX) / 2) * (50 * curvature);
+
+ const hx1 = hasCurvature && distanceX < 0 ? sourceX + amtX : cX;
+ const hx2 = hasCurvature && distanceX < 0 ? targetX - amtX : cX;
+
+ return `M${sourceX},${sourceY} C${hx1},${sourceY} ${hx2},${targetY}, ${targetX},${targetY}`;
+ } else if (leftAndRight.includes(targetPosition)) {
+ return `M${sourceX},${sourceY} Q${sourceX},${targetY} ${targetX},${targetY}`;
+ } else if (leftAndRight.includes(sourcePosition)) {
+ return `M${sourceX},${sourceY} Q${targetX},${sourceY} ${targetX},${targetY}`;
+ }
+
+ cY = typeof centerY !== 'undefined' ? centerY : _centerY;
+ const distanceY = targetY - sourceY;
+
+ const absDistanceY = Math.abs(Math.min(0, distanceY));
+ const amtY = (Math.sqrt(absDistanceY) / 2) * (50 * curvature);
+
+ const hy1 = hasCurvature && distanceY < 0 ? sourceY + amtY : cY;
+ const hy2 = hasCurvature && distanceY < 0 ? targetY - amtY : cY;
+
+ return `M${sourceX},${sourceY} C${sourceX},${hy1} ${targetX},${hy2} ${targetX},${targetY}`;
+}
+
+export default memo(
+ ({
+ sourceX,
+ sourceY,
+ targetX,
+ targetY,
+ sourcePosition = Position.Bottom,
+ targetPosition = Position.Top,
+ label,
+ labelStyle,
+ labelShowBg,
+ labelBgStyle,
+ labelBgPadding,
+ labelBgBorderRadius,
+ style,
+ markerEnd,
+ markerStart,
+ curvature,
+ }: EdgeProps) => {
+ const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition });
+ const path = getBezierPath({
+ sourceX,
+ sourceY,
+ sourcePosition,
+ targetX,
+ targetY,
+ targetPosition,
+ curvature,
+ });
+
+ return (
+
+ );
+ }
+);
diff --git a/src/components/Edges/SimpleBezierEdge.tsx b/src/components/Edges/SimpleBezierEdge.tsx
new file mode 100644
index 00000000..1bc671c9
--- /dev/null
+++ b/src/components/Edges/SimpleBezierEdge.tsx
@@ -0,0 +1,12 @@
+import React, { memo } from 'react';
+
+import BezierEdge, { getBezierPath, GetBezierPathParams } from './BezierEdge';
+import { EdgeProps } from '../../types';
+
+export function getSimpleBezierPath(props: GetBezierPathParams): string {
+ return getBezierPath({ ...props, curvature: 0 });
+}
+
+export default memo((props: EdgeProps) => {
+ return ;
+});
diff --git a/src/components/Edges/SmoothStepEdge.tsx b/src/components/Edges/SmoothStepEdge.tsx
index b5a95c21..4eb1ba6b 100644
--- a/src/components/Edges/SmoothStepEdge.tsx
+++ b/src/components/Edges/SmoothStepEdge.tsx
@@ -1,8 +1,8 @@
import React, { memo } from 'react';
-import EdgeText from './EdgeText';
-import { getMarkerEnd, getCenter } from './utils';
+import { getCenter } from './utils';
import { EdgeSmoothStepProps, Position } from '../../types';
+import BaseEdge from './BaseEdge';
// These are some helper methods for drawing the round corners
// The name indicates the direction of the path. "bottomLeftCorner" goes
@@ -21,7 +21,7 @@ const topLeftCorner = (x: number, y: number, size: number): string => `L ${x},${
const topRightCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}`;
const rightTopCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}`;
-interface GetSmoothStepPathParams {
+export interface GetSmoothStepPathParams {
sourceX: number;
sourceY: number;
sourcePosition?: Position;
@@ -130,8 +130,8 @@ export default memo(
style,
sourcePosition = Position.Bottom,
targetPosition = Position.Top,
- arrowHeadType,
- markerEndId,
+ markerEnd,
+ markerStart,
borderRadius = 5,
}: EdgeSmoothStepProps) => {
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition });
@@ -146,26 +146,21 @@ export default memo(
borderRadius,
});
- const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
-
- const text = label ? (
-
- ) : null;
-
- return (
- <>
-
- {text}
- >
);
}
);
diff --git a/src/components/Edges/StraightEdge.tsx b/src/components/Edges/StraightEdge.tsx
index 1bd3dedf..45357615 100644
--- a/src/components/Edges/StraightEdge.tsx
+++ b/src/components/Edges/StraightEdge.tsx
@@ -1,7 +1,6 @@
import React, { memo } from 'react';
-import EdgeText from './EdgeText';
-import { getMarkerEnd } from './utils';
+import BaseEdge from './BaseEdge';
import { EdgeProps } from '../../types';
export default memo(
@@ -17,39 +16,31 @@ export default memo(
labelBgPadding,
labelBgBorderRadius,
style,
- arrowHeadType,
- markerEndId,
+ markerEnd,
+ markerStart,
}: EdgeProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
- const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
+ const path = `M ${sourceX},${sourceY}L ${targetX},${targetY}`;
- const text = label ? (
-
- ) : null;
-
- return (
- <>
-
- {text}
- >
);
}
);
diff --git a/src/components/Edges/index.ts b/src/components/Edges/index.ts
index 65beb017..e4d30109 100644
--- a/src/components/Edges/index.ts
+++ b/src/components/Edges/index.ts
@@ -1,4 +1,5 @@
-export { default as BezierEdge } from './BezierEdge';
-export { default as StepEdge } from './StepEdge';
+export { default as SimpleBezierEdge } from './SimpleBezierEdge';
export { default as SmoothStepEdge } from './SmoothStepEdge';
+export { default as StepEdge } from './StepEdge';
export { default as StraightEdge } from './StraightEdge';
+export { default as BezierEdge } from './BezierEdge';
diff --git a/src/components/Edges/utils.ts b/src/components/Edges/utils.ts
index 912fd58f..eb4f5127 100644
--- a/src/components/Edges/utils.ts
+++ b/src/components/Edges/utils.ts
@@ -1,11 +1,11 @@
-import { ArrowHeadType, Position } from '../../types';
+import { MarkerType, Position } from '../../types';
-export const getMarkerEnd = (arrowHeadType?: ArrowHeadType, markerEndId?: string): string => {
+export const getMarkerEnd = (markerType?: MarkerType, markerEndId?: string): string => {
if (typeof markerEndId !== 'undefined' && markerEndId) {
return `url(#${markerEndId})`;
}
- return typeof arrowHeadType !== 'undefined' ? `url(#react-flow__${arrowHeadType})` : 'none';
+ return typeof markerType !== 'undefined' ? `url(#react-flow__${markerType})` : 'none';
};
export interface GetCenterParams {
diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx
index 60384e74..e8b0f3a0 100644
--- a/src/components/Edges/wrapEdge.tsx
+++ b/src/components/Edges/wrapEdge.tsx
@@ -1,10 +1,17 @@
import React, { memo, ComponentType, useCallback, useState, useMemo } from 'react';
import cc from 'classcat';
+import shallow from 'zustand/shallow';
-import { useStoreActions, useStoreState } from '../../store/hooks';
-import { Edge, EdgeProps, WrapEdgeProps } from '../../types';
+import { useStore, useStoreApi } from '../../store';
+import { Edge, EdgeProps, WrapEdgeProps, ReactFlowState, Connection } from '../../types';
import { onMouseDown } from '../../components/Handle/handler';
import { EdgeAnchor } from './EdgeAnchor';
+import { getMarkerId } from '../../utils/graph';
+
+const selector = (s: ReactFlowState) => ({
+ addSelectedEdges: s.addSelectedEdges,
+ connectionMode: s.connectionMode,
+});
export default (EdgeComponent: ComponentType) => {
const EdgeWrapper = ({
@@ -23,7 +30,6 @@ export default (EdgeComponent: ComponentType) => {
labelBgPadding,
labelBgBorderRadius,
style,
- arrowHeadType,
source,
target,
sourceX,
@@ -33,29 +39,27 @@ export default (EdgeComponent: ComponentType) => {
sourcePosition,
targetPosition,
elementsSelectable,
- markerEndId,
- isHidden,
+ hidden,
sourceHandleId,
targetHandleId,
- handleEdgeUpdate,
- onConnectEdge,
onContextMenu,
onMouseEnter,
onMouseMove,
onMouseLeave,
edgeUpdaterRadius,
+ onEdgeUpdate,
onEdgeUpdateStart,
onEdgeUpdateEnd,
+ markerEnd,
+ markerStart,
}: WrapEdgeProps): JSX.Element | null => {
- const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
- const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
- const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
- const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
- const connectionMode = useStoreState((state) => state.connectionMode);
+ const store = useStoreApi();
+ const { addSelectedEdges, connectionMode } = useStore(selector, shallow);
const [updating, setUpdating] = useState(false);
const inactive = !elementsSelectable && !onClick;
+ const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined';
const edgeClasses = cc([
'react-flow__edge',
`react-flow__edge-${type}`,
@@ -89,8 +93,8 @@ export default (EdgeComponent: ComponentType) => {
const onEdgeClick = useCallback(
(event: React.MouseEvent): void => {
if (elementsSelectable) {
- unsetNodesSelection();
- addSelectedElements(edgeElement);
+ store.setState({ nodesSelectionActive: false });
+ addSelectedEdges([edgeElement.id]);
}
onClick?.(event, edgeElement);
@@ -146,21 +150,30 @@ export default (EdgeComponent: ComponentType) => {
? (evt: MouseEvent): void => onEdgeUpdateEnd(evt, edgeElement)
: undefined;
+ const onConnectEdge = (connection: Connection) => {
+ const { edges } = store.getState();
+ const edge = edges.find((e) => e.id === id);
+
+ if (edge && onEdgeUpdate) {
+ onEdgeUpdate(edge, connection);
+ }
+ };
+
onMouseDown(
event,
handleId,
nodeId,
- setConnectionNodeId,
- setPosition,
+ store.setState,
onConnectEdge,
isTarget,
isValidConnection,
connectionMode,
isSourceHandle ? 'target' : 'source',
- _onEdgeUpdate
+ _onEdgeUpdate,
+ store.getState
);
},
- [id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition, edgeElement, onConnectEdge]
+ [id, source, target, type, sourceHandleId, targetHandleId, edgeElement, onEdgeUpdate]
);
const onEdgeUpdaterSourceMouseDown = useCallback(
@@ -179,8 +192,10 @@ export default (EdgeComponent: ComponentType) => {
const onEdgeUpdaterMouseEnter = useCallback(() => setUpdating(true), [setUpdating]);
const onEdgeUpdaterMouseOut = useCallback(() => setUpdating(false), [setUpdating]);
+ const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart)})`, [markerStart]);
+ const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd)})`, [markerEnd]);
- if (isHidden) {
+ if (hidden) {
return null;
}
@@ -208,16 +223,16 @@ export default (EdgeComponent: ComponentType) => {
labelBgBorderRadius={labelBgBorderRadius}
data={data}
style={style}
- arrowHeadType={arrowHeadType}
sourceX={sourceX}
sourceY={sourceY}
targetX={targetX}
targetY={targetY}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
- markerEndId={markerEndId}
sourceHandleId={sourceHandleId}
targetHandleId={targetHandleId}
+ markerStart={markerStartUrl}
+ markerEnd={markerEndUrl}
/>
{handleEdgeUpdate && (
{
- const setElements = useStoreActions((actions) => actions.setElements);
-
- useEffect(() => {
- setElements(elements);
- }, [elements]);
-
- return null;
-};
-
-export default ElementUpdater;
diff --git a/src/components/Handle/handler.ts b/src/components/Handle/handler.ts
index 03fd37df..c695c557 100644
--- a/src/components/Handle/handler.ts
+++ b/src/components/Handle/handler.ts
@@ -1,24 +1,19 @@
import { MouseEvent as ReactMouseEvent } from 'react';
+import { SetState } from 'zustand';
import { getHostForElement } from '../../utils';
-
import {
- ElementId,
- XYPosition,
- OnConnectFunc,
- OnConnectStartFunc,
- OnConnectStopFunc,
- OnConnectEndFunc,
+ OnConnect,
+ OnConnectStart,
+ OnConnectStop,
+ OnConnectEnd,
ConnectionMode,
- SetConnectionId,
Connection,
HandleType,
+ ReactFlowState,
} from '../../types';
type ValidConnectionFunc = (connection: Connection) => boolean;
-export type SetSourceIdFunc = (params: SetConnectionId) => void;
-
-export type SetPosition = (pos: XYPosition) => void;
type Result = {
elementBelow: Element | null;
@@ -28,17 +23,15 @@ type Result = {
};
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
-function checkElementBelowIsValid(
+export function checkElementBelowIsValid(
event: MouseEvent,
connectionMode: ConnectionMode,
isTarget: boolean,
- nodeId: ElementId,
- handleId: ElementId | null,
+ nodeId: string,
+ handleId: string | null,
isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot
) {
- // TODO: why does this throw an error? elementFromPoint should be available for ShadowRoot too
- // @ts-ignore
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY);
const elementBelowIsTarget = elementBelow?.classList.contains('target') || false;
const elementBelowIsSource = elementBelow?.classList.contains('source') || false;
@@ -91,19 +84,18 @@ function resetRecentHandle(hoveredHandle: Element): void {
export function onMouseDown(
event: ReactMouseEvent,
- handleId: ElementId | null,
- nodeId: ElementId,
- setConnectionNodeId: SetSourceIdFunc,
- setPosition: SetPosition,
- onConnect: OnConnectFunc,
+ handleId: string | null,
+ nodeId: string,
+ setState: SetState,
+ onConnect: OnConnect,
isTarget: boolean,
isValidConnection: ValidConnectionFunc,
connectionMode: ConnectionMode,
elementEdgeUpdaterType?: HandleType,
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
- onConnectStart?: OnConnectStartFunc,
- onConnectStop?: OnConnectStopFunc,
- onConnectEnd?: OnConnectEndFunc
+ onConnectStart?: OnConnectStart,
+ onConnectStop?: OnConnectStop,
+ onConnectEnd?: OnConnectEnd
): void {
const reactFlowNode = (event.target as Element).closest('.react-flow');
// when react-flow is used inside a shadow root we can't use document
@@ -113,7 +105,6 @@ export function onMouseDown(
return;
}
- // @ts-ignore
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY);
const elementBelowIsTarget = elementBelow?.classList.contains('target');
const elementBelowIsSource = elementBelow?.classList.contains('source');
@@ -126,18 +117,24 @@ export function onMouseDown(
const containerBounds = reactFlowNode.getBoundingClientRect();
let recentHoveredHandle: Element;
- setPosition({
- x: event.clientX - containerBounds.left,
- y: event.clientY - containerBounds.top,
+ setState({
+ connectionPosition: {
+ x: event.clientX - containerBounds.left,
+ y: event.clientY - containerBounds.top,
+ },
+ connectionNodeId: nodeId,
+ connectionHandleId: handleId,
+ connectionHandleType: handleType,
});
- setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleId: handleId, connectionHandleType: handleType });
onConnectStart?.(event, { nodeId, handleId, handleType });
function onMouseMove(event: MouseEvent) {
- setPosition({
- x: event.clientX - containerBounds.left,
- y: event.clientY - containerBounds.top,
+ setState({
+ connectionPosition: {
+ x: event.clientX - containerBounds.left,
+ y: event.clientY - containerBounds.top,
+ },
});
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
@@ -187,7 +184,11 @@ export function onMouseDown(
}
resetRecentHandle(recentHoveredHandle);
- setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null });
+ setState({
+ connectionNodeId: null,
+ connectionHandleId: null,
+ connectionHandleType: null,
+ });
doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject);
doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject);
diff --git a/src/components/Handle/index.tsx b/src/components/Handle/index.tsx
index 9a38c550..93c9a5a1 100644
--- a/src/components/Handle/index.tsx
+++ b/src/components/Handle/index.tsx
@@ -1,16 +1,29 @@
import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from 'react';
import cc from 'classcat';
+import shallow from 'zustand/shallow';
-import { useStoreActions, useStoreState } from '../../store/hooks';
+import { useStore, useStoreApi } from '../../store';
import NodeIdContext from '../../contexts/NodeIdContext';
-import { HandleProps, Connection, ElementId, Position } from '../../types';
-
-import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
+import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
+import { checkElementBelowIsValid, onMouseDown } from './handler';
+import { getHostForElement } from '../../utils';
+import { addEdge } from '../../utils/graph';
const alwaysValid = () => true;
export type HandleComponentProps = HandleProps & Omit, 'id'>;
+const selector = (s: ReactFlowState) => ({
+ onConnectAction: s.onConnect,
+ onConnectStart: s.onConnectStart,
+ onConnectStop: s.onConnectStop,
+ onConnectEnd: s.onConnectEnd,
+ connectionMode: s.connectionMode,
+ connectionStartHandle: s.connectionStartHandle,
+ connectOnClick: s.connectOnClick,
+ hasDefaultEdges: s.hasDefaultEdges,
+});
+
const Handle = forwardRef(
(
{
@@ -26,49 +39,64 @@ const Handle = forwardRef(
},
ref
) => {
- const nodeId = useContext(NodeIdContext) as ElementId;
- const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
- const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
- const onConnectAction = useStoreState((state) => state.onConnect);
- const onConnectStart = useStoreState((state) => state.onConnectStart);
- const onConnectStop = useStoreState((state) => state.onConnectStop);
- const onConnectEnd = useStoreState((state) => state.onConnectEnd);
- const connectionMode = useStoreState((state) => state.connectionMode);
+ const store = useStoreApi();
+ const nodeId = useContext(NodeIdContext) as string;
+ const {
+ onConnectAction,
+ onConnectStart,
+ onConnectStop,
+ onConnectEnd,
+ connectionMode,
+ connectionStartHandle,
+ connectOnClick,
+ hasDefaultEdges,
+ } = useStore(selector, shallow);
+
const handleId = id || null;
const isTarget = type === 'target';
const onConnectExtended = useCallback(
(params: Connection) => {
- onConnectAction?.(params);
- onConnect?.(params);
+ const { defaultEdgeOptions } = store.getState();
+
+ const edgeParams = {
+ ...defaultEdgeOptions,
+ ...params,
+ };
+ if (hasDefaultEdges) {
+ const { edges } = store.getState();
+ store.setState({ edges: addEdge(edgeParams, edges) });
+ }
+
+ onConnectAction?.(edgeParams);
+ onConnect?.(edgeParams);
},
- [onConnectAction, onConnect]
+ [hasDefaultEdges, onConnectAction, onConnect]
);
const onMouseDownHandler = useCallback(
(event: React.MouseEvent) => {
- onMouseDown(
- event,
- handleId,
- nodeId,
- setConnectionNodeId as unknown as SetSourceIdFunc,
- setPosition as unknown as SetPosition,
- onConnectExtended,
- isTarget,
- isValidConnection,
- connectionMode,
- undefined,
- undefined,
- onConnectStart,
- onConnectStop,
- onConnectEnd
- );
+ if (event.button === 0) {
+ onMouseDown(
+ event,
+ handleId,
+ nodeId,
+ store.setState,
+ onConnectExtended,
+ isTarget,
+ isValidConnection,
+ connectionMode,
+ undefined,
+ undefined,
+ onConnectStart,
+ onConnectStop,
+ onConnectEnd
+ );
+ }
},
[
handleId,
nodeId,
- setConnectionNodeId,
- setPosition,
onConnectExtended,
isTarget,
isValidConnection,
@@ -79,6 +107,47 @@ const Handle = forwardRef(
]
);
+ const onClick = useCallback(
+ (event: React.MouseEvent) => {
+ if (!connectionStartHandle) {
+ onConnectStart?.(event, { nodeId, handleId, handleType: type });
+ store.setState({ connectionStartHandle: { nodeId, type, handleId } });
+ } else {
+ const doc = getHostForElement(event.target as HTMLElement);
+ const { connection, isValid } = checkElementBelowIsValid(
+ event as unknown as MouseEvent,
+ connectionMode,
+ connectionStartHandle.type === 'target',
+ connectionStartHandle.nodeId,
+ connectionStartHandle.handleId || null,
+ isValidConnection,
+ doc
+ );
+
+ onConnectStop?.(event as unknown as MouseEvent);
+
+ if (isValid) {
+ onConnectExtended(connection);
+ }
+
+ onConnectEnd?.(event as unknown as MouseEvent);
+
+ store.setState({ connectionStartHandle: null });
+ }
+ },
+ [
+ connectionStartHandle,
+ onConnectStart,
+ onConnectExtended,
+ onConnectStop,
+ onConnectEnd,
+ isTarget,
+ nodeId,
+ handleId,
+ type,
+ ]
+ );
+
const handleClasses = cc([
'react-flow__handle',
`react-flow__handle-${position}`,
@@ -88,6 +157,10 @@ const Handle = forwardRef(
source: !isTarget,
target: isTarget,
connectable: isConnectable,
+ connecting:
+ connectionStartHandle?.nodeId === nodeId &&
+ connectionStartHandle?.handleId === handleId &&
+ connectionStartHandle?.type === type,
},
]);
@@ -98,6 +171,7 @@ const Handle = forwardRef(
data-handlepos={position}
className={handleClasses}
onMouseDown={onMouseDownHandler}
+ onClick={connectOnClick ? onClick : undefined}
ref={ref}
{...rest}
>
diff --git a/src/components/Nodes/DefaultNode.tsx b/src/components/Nodes/DefaultNode.tsx
index 11d9af2f..353bd7ca 100644
--- a/src/components/Nodes/DefaultNode.tsx
+++ b/src/components/Nodes/DefaultNode.tsx
@@ -8,13 +8,15 @@ const DefaultNode = ({
isConnectable,
targetPosition = Position.Top,
sourcePosition = Position.Bottom,
-}: NodeProps) => (
- <>
-
- {data.label}
-
- >
-);
+}: NodeProps) => {
+ return (
+ <>
+
+ {data?.label}
+
+ >
+ );
+};
DefaultNode.displayName = 'DefaultNode';
diff --git a/src/components/Nodes/GroupNode.tsx b/src/components/Nodes/GroupNode.tsx
new file mode 100644
index 00000000..cea42e43
--- /dev/null
+++ b/src/components/Nodes/GroupNode.tsx
@@ -0,0 +1,5 @@
+const GroupNode = () => null;
+
+GroupNode.displayName = 'GroupNode';
+
+export default GroupNode;
diff --git a/src/components/Nodes/InputNode.tsx b/src/components/Nodes/InputNode.tsx
index 73ac0cdf..e2546762 100644
--- a/src/components/Nodes/InputNode.tsx
+++ b/src/components/Nodes/InputNode.tsx
@@ -5,7 +5,7 @@ import { NodeProps, Position } from '../../types';
const InputNode = ({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) => (
<>
- {data.label}
+ {data?.label}
>
);
diff --git a/src/components/Nodes/OutputNode.tsx b/src/components/Nodes/OutputNode.tsx
index b6358414..1bc913b7 100644
--- a/src/components/Nodes/OutputNode.tsx
+++ b/src/components/Nodes/OutputNode.tsx
@@ -6,7 +6,7 @@ import { NodeProps, Position } from '../../types';
const OutputNode = ({ data, isConnectable, targetPosition = Position.Top }: NodeProps) => (
<>
- {data.label}
+ {data?.label}
>
);
diff --git a/src/components/Nodes/useMemoizedMouseHandler.ts b/src/components/Nodes/useMemoizedMouseHandler.ts
new file mode 100644
index 00000000..36334695
--- /dev/null
+++ b/src/components/Nodes/useMemoizedMouseHandler.ts
@@ -0,0 +1,25 @@
+import { MouseEvent, useCallback } from 'react';
+import { GetState } from 'zustand';
+
+import { ReactFlowState, Node } from '../../types';
+
+function useMemoizedMouseHandler(
+ id: string,
+ dragging: boolean,
+ getState: GetState,
+ handler?: (event: MouseEvent, node: Node) => void
+) {
+ const memoizedHandler = useCallback(
+ (event: MouseEvent) => {
+ if (typeof handler !== 'undefined' && !dragging) {
+ const node = getState().nodeInternals.get(id)!;
+ handler(event, { ...node });
+ }
+ },
+ [handler, dragging, id]
+ );
+
+ return memoizedHandler;
+}
+
+export default useMemoizedMouseHandler;
diff --git a/src/components/Nodes/utils.ts b/src/components/Nodes/utils.ts
index e917e853..1ed82013 100644
--- a/src/components/Nodes/utils.ts
+++ b/src/components/Nodes/utils.ts
@@ -13,7 +13,7 @@ export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
export const getHandleBoundsByHandleType = (
selector: string,
nodeElement: HTMLDivElement,
- parentBounds: ClientRect | DOMRect,
+ parentBounds: DOMRect,
k: number
): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector);
@@ -24,20 +24,18 @@ export const getHandleBoundsByHandleType = (
const handlesArray = Array.from(handles) as HTMLDivElement[];
- return handlesArray.map(
- (handle): HandleElement => {
- const bounds = handle.getBoundingClientRect();
- const dimensions = getDimensions(handle);
- const handleId = handle.getAttribute('data-handleid');
- const handlePosition = (handle.getAttribute('data-handlepos') as unknown) as Position;
+ return handlesArray.map((handle): HandleElement => {
+ const bounds = handle.getBoundingClientRect();
+ const dimensions = getDimensions(handle);
+ const handleId = handle.getAttribute('data-handleid');
+ const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
- return {
- id: handleId,
- position: handlePosition,
- x: (bounds.left - parentBounds.left) / k,
- y: (bounds.top - parentBounds.top) / k,
- ...dimensions,
- };
- }
- );
+ return {
+ id: handleId,
+ position: handlePosition,
+ x: (bounds.left - parentBounds.left) / k,
+ y: (bounds.top - parentBounds.top) / k,
+ ...dimensions,
+ };
+ });
};
diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx
index a6902416..04fc457b 100644
--- a/src/components/Nodes/wrapNode.tsx
+++ b/src/components/Nodes/wrapNode.tsx
@@ -1,22 +1,21 @@
-import React, {
- useEffect,
- useLayoutEffect,
- useRef,
- memo,
- ComponentType,
- CSSProperties,
- useMemo,
- MouseEvent,
- useCallback,
-} from 'react';
+import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react';
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
import cc from 'classcat';
+import shallow from 'zustand/shallow';
-import { useStoreActions } from '../../store/hooks';
+import { useStore, useStoreApi } from '../../store';
import { Provider } from '../../contexts/NodeIdContext';
-import { NodeComponentProps, WrapNodeProps } from '../../types';
+import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
+import useMemoizedMouseHandler from './useMemoizedMouseHandler';
-export default (NodeComponent: ComponentType) => {
+const selector = (s: ReactFlowState) => ({
+ addSelectedNodes: s.addSelectedNodes,
+ updateNodePosition: s.updateNodePosition,
+ unselectNodesAndEdges: s.unselectNodesAndEdges,
+ updateNodeDimensions: s.updateNodeDimensions,
+});
+
+export default (NodeComponent: ComponentType) => {
const NodeWrapper = ({
id,
type,
@@ -42,192 +41,188 @@ export default (NodeComponent: ComponentType) => {
selectNodesOnDrag,
sourcePosition,
targetPosition,
- isHidden,
- isInitialized,
+ hidden,
snapToGrid,
snapGrid,
- isDragging,
+ dragging,
resizeObserver,
dragHandle,
+ zIndex,
+ isParent,
+ noPanClassName,
+ noDragClassName,
}: WrapNodeProps) => {
- const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
- const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
- const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff);
- const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
-
+ const store = useStoreApi();
+ const { addSelectedNodes, unselectNodesAndEdges, updateNodePosition, updateNodeDimensions } = useStore(
+ selector,
+ shallow
+ );
const nodeElement = useRef(null);
-
- const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
- const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]);
-
+ const prevSourcePosition = useRef(sourcePosition);
+ const prevTargetPosition = useRef(targetPosition);
+ const prevType = useRef(type);
+ const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
const nodeStyle: CSSProperties = useMemo(
() => ({
- zIndex: selected ? 10 : 3,
+ zIndex,
transform: `translate(${xPos}px,${yPos}px)`,
- pointerEvents:
- isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none',
- // prevents jumping of nodes on start
- opacity: isInitialized ? 1 : 0,
+ pointerEvents: hasPointerEvents ? 'all' : 'none',
...style,
}),
- [
- selected,
- xPos,
- yPos,
- isSelectable,
- isDraggable,
- onClick,
- isInitialized,
- style,
- onMouseEnter,
- onMouseMove,
- onMouseLeave,
- ]
+ [zIndex, xPos, yPos, hasPointerEvents, style]
);
- const onMouseEnterHandler = useMemo(() => {
- if (!onMouseEnter || isDragging) {
- return;
- }
- return (event: MouseEvent) => onMouseEnter(event, node);
- }, [onMouseEnter, isDragging, node]);
+ const grid = useMemo(
+ () => (snapToGrid ? snapGrid : [1, 1])! as [number, number],
+ [snapToGrid, snapGrid?.[0], snapGrid?.[1]]
+ );
- const onMouseMoveHandler = useMemo(() => {
- if (!onMouseMove || isDragging) {
- return;
- }
-
- return (event: MouseEvent) => onMouseMove(event, node);
- }, [onMouseMove, isDragging, node]);
-
- const onMouseLeaveHandler = useMemo(() => {
- if (!onMouseLeave || isDragging) {
- return;
- }
-
- return (event: MouseEvent) => onMouseLeave(event, node);
- }, [onMouseLeave, isDragging, node]);
-
- const onContextMenuHandler = useMemo(() => {
- if (!onContextMenu) {
- return;
- }
-
- return (event: MouseEvent) => onContextMenu(event, node);
- }, [onContextMenu, node]);
+ const onMouseEnterHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseEnter);
+ const onMouseMoveHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseMove);
+ const onMouseLeaveHandler = useMemoizedMouseHandler(id, dragging, store.getState, onMouseLeave);
+ const onContextMenuHandler = useMemoizedMouseHandler(id, false, store.getState, onContextMenu);
+ const onNodeDoubleClickHandler = useMemoizedMouseHandler(id, false, store.getState, onNodeDoubleClick);
const onSelectNodeHandler = useCallback(
(event: MouseEvent) => {
if (!isDraggable) {
if (isSelectable) {
- unsetNodesSelection();
+ store.setState({ nodesSelectionActive: false });
if (!selected) {
- addSelectedElements(node);
+ addSelectedNodes([id]);
}
}
- onClick?.(event, node);
+ if (onClick) {
+ const node = store.getState().nodeInternals.get(id)!;
+ onClick(event, { ...node });
+ }
}
},
- [isSelectable, selected, isDraggable, onClick, node]
+ [isSelectable, selected, isDraggable, onClick, id]
);
const onDragStart = useCallback(
(event: DraggableEvent) => {
- onNodeDragStart?.(event as MouseEvent, node);
-
if (selectNodesOnDrag && isSelectable) {
- unsetNodesSelection();
+ store.setState({ nodesSelectionActive: false });
if (!selected) {
- addSelectedElements(node);
+ addSelectedNodes([id]);
}
} else if (!selectNodesOnDrag && !selected && isSelectable) {
- unsetNodesSelection();
- addSelectedElements([]);
+ unselectNodesAndEdges();
+ store.setState({ nodesSelectionActive: false });
+ }
+
+ if (onNodeDragStart) {
+ const node = store.getState().nodeInternals.get(id)!;
+ onNodeDragStart(event as MouseEvent, { ...node });
}
},
- [node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
+ [id, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
);
const onDrag = useCallback(
(event: DraggableEvent, draggableData: DraggableData) => {
- if (onNodeDrag) {
- node.position.x += draggableData.deltaX;
- node.position.y += draggableData.deltaY;
- onNodeDrag(event as MouseEvent, node);
- }
+ updateNodePosition({ id, dragging: true, diff: { x: draggableData.deltaX, y: draggableData.deltaY } });
- updateNodePosDiff({
- id,
- diff: {
- x: draggableData.deltaX,
- y: draggableData.deltaY,
- },
- isDragging: true,
- });
+ if (onNodeDrag) {
+ const node = store.getState().nodeInternals.get(id)!;
+ onNodeDrag(event as MouseEvent, {
+ ...node,
+ dragging: true,
+ position: {
+ x: node.position.x + draggableData.deltaX,
+ y: node.position.y + draggableData.deltaY,
+ },
+ positionAbsolute: {
+ x: (node.positionAbsolute?.x || 0) + draggableData.deltaX,
+ y: (node.positionAbsolute?.y || 0) + draggableData.deltaY,
+ },
+ });
+ }
},
- [id, node, onNodeDrag]
+ [id, onNodeDrag]
);
const onDragStop = useCallback(
(event: DraggableEvent) => {
// onDragStop also gets called when user just clicks on a node.
// Because of that we set dragging to true inside the onDrag handler and handle the click here
- if (!isDragging) {
+ let node;
+
+ if (onClick || onNodeDragStop) {
+ node = store.getState().nodeInternals.get(id)!;
+ }
+
+ if (!dragging) {
if (isSelectable && !selectNodesOnDrag && !selected) {
- addSelectedElements(node);
+ addSelectedNodes([id]);
}
- onClick?.(event as MouseEvent, node);
+ if (onClick && node) {
+ onClick(event as MouseEvent, { ...node });
+ }
return;
}
- updateNodePosDiff({
- id: node.id,
- isDragging: false,
+ updateNodePosition({
+ id,
+ dragging: false,
});
- onNodeDragStop?.(event as MouseEvent, node);
+ if (onNodeDragStop && node) {
+ onNodeDragStop(event as MouseEvent, { ...node, dragging: false });
+ }
},
- [node, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, isDragging, selected]
+ [id, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, dragging, selected]
);
- const onNodeDoubleClickHandler = useCallback(
- (event: MouseEvent) => {
- onNodeDoubleClick?.(event, node);
- },
- [node, onNodeDoubleClick]
- );
-
- useLayoutEffect(() => {
- if (nodeElement.current && !isHidden) {
- updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
- }
- }, [id, isHidden, sourcePosition, targetPosition]);
-
useEffect(() => {
- if (nodeElement.current) {
+ if (nodeElement.current && !hidden) {
const currNode = nodeElement.current;
resizeObserver?.observe(currNode);
return () => resizeObserver?.unobserve(currNode);
}
- }, []);
+ }, [hidden]);
- if (isHidden) {
+ useEffect(() => {
+ // when the user programmatically changes the source or handle position, we re-initialize the node
+ const typeChanged = prevType.current !== type;
+ const sourcePosChanged = prevSourcePosition.current !== sourcePosition;
+ const targetPosChanged = prevTargetPosition.current !== targetPosition;
+
+ if (nodeElement.current && (typeChanged || sourcePosChanged || targetPosChanged)) {
+ if (typeChanged) {
+ prevType.current = type;
+ }
+ if (sourcePosChanged) {
+ prevSourcePosition.current = sourcePosition;
+ }
+ if (targetPosChanged) {
+ prevTargetPosition.current = targetPosition;
+ }
+ updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
+ }
+ }, [id, type, sourcePosition, targetPosition]);
+
+ if (hidden) {
return null;
}
const nodeClasses = cc([
'react-flow__node',
`react-flow__node-${type}`,
+ noPanClassName,
className,
{
selected,
selectable: isSelectable,
+ parent: isParent,
},
]);
@@ -238,7 +233,7 @@ export default (NodeComponent: ComponentType) => {
onStop={onDragStop}
scale={scale}
disabled={!isDraggable}
- cancel=".nodrag"
+ cancel={`.${noDragClassName}`}
nodeRef={nodeElement}
grid={grid}
enableUserSelectHack={false}
@@ -267,8 +262,9 @@ export default (NodeComponent: ComponentType) => {
isConnectable={isConnectable}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
- isDragging={isDragging}
+ dragging={dragging}
dragHandle={dragHandle}
+ zIndex={zIndex}
/>
diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx
index efb780e7..0924ff11 100644
--- a/src/components/NodesSelection/index.tsx
+++ b/src/components/NodesSelection/index.tsx
@@ -1,57 +1,53 @@
/**
* The nodes selection rectangle gets displayed when a user
- * made a selectio with on or several nodes
+ * made a selection with on or several nodes
*/
-import React, { useMemo, useCallback, useRef, MouseEvent } from 'react';
-import ReactDraggable, { DraggableData } from 'react-draggable';
+import React, { memo, useMemo, useCallback, useRef, MouseEvent } from 'react';
+import { DraggableCore, DraggableData } from 'react-draggable';
+import cc from 'classcat';
+import shallow from 'zustand/shallow';
-import { useStoreState, useStoreActions } from '../../store/hooks';
-import { isNode } from '../../utils/graph';
-import { Node } from '../../types';
+import { useStore } from '../../store';
+import { Node, ReactFlowState } from '../../types';
+import { getRectOfNodes } from '../../utils/graph';
export interface NodesSelectionProps {
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
onSelectionDrag?: (event: MouseEvent, nodes: Node[]) => void;
onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void;
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
+ noPanClassName?: string;
}
+// @TODO: work with nodeInternals instead of converting it to an array
+const selector = (s: ReactFlowState) => ({
+ transform: s.transform,
+ selectedNodesBbox: s.selectedNodesBbox,
+ userSelectionActive: s.userSelectionActive,
+ selectedNodes: Array.from(s.nodeInternals)
+ .filter(([_, n]) => n.selected)
+ .map(([_, n]) => n),
+ snapToGrid: s.snapToGrid,
+ snapGrid: s.snapGrid,
+ updateNodePosition: s.updateNodePosition,
+});
-export default ({
+function NodesSelection({
onSelectionDragStart,
onSelectionDrag,
onSelectionDragStop,
onSelectionContextMenu,
-}: NodesSelectionProps) => {
- const [tX, tY, tScale] = useStoreState((state) => state.transform);
- const selectedNodesBbox = useStoreState((state) => state.selectedNodesBbox);
- const selectionActive = useStoreState((state) => state.selectionActive);
- const selectedElements = useStoreState((state) => state.selectedElements);
- const snapToGrid = useStoreState((state) => state.snapToGrid);
- const snapGrid = useStoreState((state) => state.snapGrid);
- const nodes = useStoreState((state) => state.nodes);
-
- const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff);
-
+ noPanClassName,
+}: NodesSelectionProps) {
+ const { transform, userSelectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosition } = useStore(
+ selector,
+ shallow
+ );
+ const [tX, tY, tScale] = transform;
const nodeRef = useRef(null);
const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]);
- const selectedNodes = useMemo(
- () =>
- selectedElements
- ? selectedElements.filter(isNode).map((selectedNode) => {
- const matchingNode = nodes.find((node) => node.id === selectedNode.id);
-
- return {
- ...matchingNode,
- position: matchingNode?.__rf.position,
- } as Node;
- })
- : [],
- [selectedElements, nodes]
- );
-
const style = useMemo(
() => ({
transform: `translate(${tX}px,${tY}px) scale(${tScale})`,
@@ -59,6 +55,8 @@ export default ({
[tX, tY, tScale]
);
+ const selectedNodesBbox = useMemo(() => getRectOfNodes(selectedNodes), [selectedNodes]);
+
const innerStyle = useMemo(
() => ({
width: selectedNodesBbox.width,
@@ -78,25 +76,23 @@ export default ({
const onDrag = useCallback(
(event: MouseEvent, data: DraggableData) => {
- if (onSelectionDrag) {
- onSelectionDrag(event, selectedNodes);
- }
-
- updateNodePosDiff({
+ updateNodePosition({
diff: {
x: data.deltaX,
y: data.deltaY,
},
- isDragging: true,
+ dragging: true,
});
+
+ onSelectionDrag?.(event, selectedNodes);
},
- [onSelectionDrag, selectedNodes, updateNodePosDiff]
+ [onSelectionDrag, selectedNodes, updateNodePosition]
);
const onStop = useCallback(
(event: MouseEvent) => {
- updateNodePosDiff({
- isDragging: false,
+ updateNodePosition({
+ dragging: false,
});
onSelectionDragStop?.(event, selectedNodes);
@@ -106,22 +102,18 @@ export default ({
const onContextMenu = useCallback(
(event: MouseEvent) => {
- const selectedNodes = selectedElements
- ? selectedElements.filter(isNode).map((selectedNode) => nodes.find((node) => node.id === selectedNode.id)!)
- : [];
-
onSelectionContextMenu?.(event, selectedNodes);
},
- [onSelectionContextMenu]
+ [onSelectionContextMenu, selectedNodes]
);
- if (!selectedElements || selectionActive) {
+ if (!selectedNodes?.length || userSelectionActive) {
return null;
}
return (
-
-
+ onStart(event as MouseEvent)}
@@ -136,7 +128,9 @@ export default ({
onContextMenu={onContextMenu}
style={innerStyle}
/>
-
+
);
-};
+}
+
+export default memo(NodesSelection);
diff --git a/src/components/SelectionListener/index.tsx b/src/components/SelectionListener/index.tsx
index 9f912794..6f052a3a 100644
--- a/src/components/SelectionListener/index.tsx
+++ b/src/components/SelectionListener/index.tsx
@@ -1,20 +1,38 @@
-import { useEffect } from 'react';
+import { memo, useEffect } from 'react';
+import shallow from 'zustand/shallow';
-import { Elements } from '../../types';
-import { useStoreState } from '../../store/hooks';
+import { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types';
+import { useStore } from '../../store';
interface SelectionListenerProps {
- onSelectionChange: (elements: Elements | null) => void;
+ onSelectionChange: OnSelectionChangeFunc;
}
-// This is a helper component for calling the onSelectionChange listener
+const selector = (s: ReactFlowState) => ({
+ selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected),
+ selectedEdges: s.edges.filter((e) => e.selected),
+});
-export default ({ onSelectionChange }: SelectionListenerProps) => {
- const selectedElements = useStoreState((s) => s.selectedElements);
+const areEqual = (objA: any, objB: any) => {
+ const selectedNodeIdsA = objA.selectedNodes.map((n: Node) => n.id);
+ const selectedNodeIdsB = objB.selectedNodes.map((n: Node) => n.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);
+};
+
+// 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 { selectedNodes, selectedEdges } = useStore(selector, areEqual);
useEffect(() => {
- onSelectionChange(selectedElements);
- }, [selectedElements]);
+ onSelectionChange({ nodes: selectedNodes, edges: selectedEdges });
+ }, [selectedNodes, selectedEdges]);
return null;
-};
+}
+
+export default memo(SelectionListener);
diff --git a/src/components/StoreUpdater/index.tsx b/src/components/StoreUpdater/index.tsx
new file mode 100644
index 00000000..afefe1b0
--- /dev/null
+++ b/src/components/StoreUpdater/index.tsx
@@ -0,0 +1,161 @@
+import { useEffect } from 'react';
+import { SetState } from 'zustand';
+import shallow from 'zustand/shallow';
+
+import { useStore, useStoreApi } from '../../store';
+import {
+ Node,
+ Edge,
+ ReactFlowState,
+ OnConnect,
+ OnConnectStart,
+ OnConnectStop,
+ OnConnectEnd,
+ CoordinateExtent,
+ OnNodesChange,
+ OnEdgesChange,
+ ConnectionMode,
+ SnapGrid,
+ DefaultEdgeOptions,
+ FitViewOptions,
+ OnNodesDelete,
+ OnEdgesDelete
+} from '../../types';
+
+interface StoreUpdaterProps {
+ nodes?: Node[];
+ edges?: Edge[];
+ defaultNodes?: Node[];
+ defaultEdges?: Edge[];
+ onConnect?: OnConnect;
+ onConnectStart?: OnConnectStart;
+ onConnectStop?: OnConnectStop;
+ onConnectEnd?: OnConnectEnd;
+ nodesDraggable?: boolean;
+ nodesConnectable?: boolean;
+ minZoom?: number;
+ maxZoom?: number;
+ nodeExtent?: CoordinateExtent;
+ onNodesChange?: OnNodesChange;
+ onEdgesChange?: OnEdgesChange;
+ elementsSelectable?: boolean;
+ connectionMode?: ConnectionMode;
+ snapToGrid?: boolean;
+ snapGrid?: SnapGrid;
+ translateExtent?: CoordinateExtent;
+ connectOnClick: boolean;
+ defaultEdgeOptions?: DefaultEdgeOptions;
+ fitView?: boolean;
+ fitViewOptions?: FitViewOptions;
+ onNodesDelete?: OnNodesDelete;
+ onEdgesDelete?: OnEdgesDelete;
+}
+
+const selector = (s: ReactFlowState) => ({
+ setNodes: s.setNodes,
+ setEdges: s.setEdges,
+ setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
+ setMinZoom: s.setMinZoom,
+ setMaxZoom: s.setMaxZoom,
+ setTranslateExtent: s.setTranslateExtent,
+ setNodeExtent: s.setNodeExtent,
+ reset: s.reset,
+});
+
+function useStoreUpdater(value: T | undefined, setStoreState: (param: T) => void) {
+ useEffect(() => {
+ if (typeof value !== 'undefined') {
+ setStoreState(value);
+ }
+ }, [value]);
+}
+
+function useDirectStoreUpdater(key: keyof ReactFlowState, value: any, setState: SetState) {
+ useEffect(() => {
+ if (typeof value !== 'undefined') {
+ // @ts-ignore
+ setState({ [key]: value });
+ }
+ }, [value]);
+}
+
+const StoreUpdater = ({
+ nodes,
+ edges,
+ defaultNodes,
+ defaultEdges,
+ onConnect,
+ onConnectStart,
+ onConnectStop,
+ onConnectEnd,
+ nodesDraggable,
+ nodesConnectable,
+ minZoom,
+ maxZoom,
+ nodeExtent,
+ onNodesChange,
+ onEdgesChange,
+ elementsSelectable,
+ connectionMode,
+ snapGrid,
+ snapToGrid,
+ translateExtent,
+ connectOnClick,
+ defaultEdgeOptions,
+ fitView,
+ fitViewOptions,
+ onNodesDelete,
+ onEdgesDelete,
+}: StoreUpdaterProps) => {
+ const {
+ setNodes,
+ setEdges,
+ setDefaultNodesAndEdges,
+ setMinZoom,
+ setMaxZoom,
+ setTranslateExtent,
+ setNodeExtent,
+ reset,
+ } = useStore(selector, shallow);
+ const store = useStoreApi();
+
+ useEffect(() => {
+ setDefaultNodesAndEdges(defaultNodes, defaultEdges);
+
+ return () => {
+ reset();
+ };
+ }, []);
+
+ useDirectStoreUpdater('defaultEdgeOptions', defaultEdgeOptions, store.setState);
+ useDirectStoreUpdater('connectionMode', connectionMode, store.setState);
+ useDirectStoreUpdater('onConnect', onConnect, store.setState);
+ useDirectStoreUpdater('onConnectStart', onConnectStart, store.setState);
+ useDirectStoreUpdater('onConnectStop', onConnectStop, store.setState);
+ useDirectStoreUpdater('onConnectEnd', onConnectEnd, store.setState);
+ useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState);
+ useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
+ useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
+ useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
+ useDirectStoreUpdater('snapGrid', snapGrid, store.setState);
+ useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState);
+ useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState);
+ useDirectStoreUpdater('connectOnClick', connectOnClick, store.setState);
+ useDirectStoreUpdater('fitViewOnInit', fitView, store.setState);
+ useDirectStoreUpdater('fitViewOnInitOptions', fitViewOptions, store.setState);
+ useDirectStoreUpdater('onNodesDelete', onNodesDelete, store.setState);
+ useDirectStoreUpdater('onEdgesDelete', onEdgesDelete, store.setState);
+
+ useStoreUpdater(nodes, setNodes);
+ useStoreUpdater(edges, setEdges);
+ useStoreUpdater(defaultNodes, setNodes);
+ useStoreUpdater(defaultEdges, setEdges);
+ useStoreUpdater(minZoom, setMinZoom);
+ useStoreUpdater(maxZoom, setMaxZoom);
+ useStoreUpdater(translateExtent, setTranslateExtent);
+ useStoreUpdater(nodeExtent, setNodeExtent);
+
+ return null;
+};
+
+export default StoreUpdater;
diff --git a/src/components/UserSelection/index.tsx b/src/components/UserSelection/index.tsx
index 14a8a13a..03f4621e 100644
--- a/src/components/UserSelection/index.tsx
+++ b/src/components/UserSelection/index.tsx
@@ -2,100 +2,160 @@
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
*/
-import React, { memo } from 'react';
+import React, { memo, useState, useRef, useCallback } from 'react';
+import shallow from 'zustand/shallow';
-import { useStoreActions, useStoreState } from '../../store/hooks';
-import { XYPosition } from '../../types';
+import { useStore, useStoreApi } from '../../store';
+import { getSelectionChanges } from '../../utils/changes';
+import { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types';
+import { getConnectedEdges, getNodesInside } from '../../utils/graph';
+
+type SelectionRect = Rect & {
+ startX: number;
+ startY: number;
+ draw: boolean;
+};
type UserSelectionProps = {
selectionKeyPressed: boolean;
};
-function getMousePosition(event: React.MouseEvent): XYPosition | void {
- const reactFlowNode = (event.target as Element).closest('.react-flow');
- if (!reactFlowNode) {
- return;
- }
-
- const containerBounds = reactFlowNode.getBoundingClientRect();
-
+function getMousePosition(event: React.MouseEvent, containerBounds: DOMRect): XYPosition {
return {
x: event.clientX - containerBounds.left,
y: event.clientY - containerBounds.top,
};
}
-const SelectionRect = () => {
- const userSelectionRect = useStoreState((state) => state.userSelectionRect);
+const selector = (s: ReactFlowState) => ({
+ userSelectionActive: s.userSelectionActive,
+ elementsSelectable: s.elementsSelectable,
+});
- if (!userSelectionRect.draw) {
- return null;
- }
-
- return (
-
- );
+const initialRect: SelectionRect = {
+ startX: 0,
+ startY: 0,
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ draw: false,
};
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
- const selectionActive = useStoreState((state) => state.selectionActive);
- const elementsSelectable = useStoreState((state) => state.elementsSelectable);
+ const store = useStoreApi();
+ const prevSelectedNodesCount = useRef(0);
+ const prevSelectedEdgesCount = useRef(0);
+ const containerBounds = useRef();
+ const [userSelectionRect, setUserSelectionRect] = useState(initialRect);
+ const { userSelectionActive, elementsSelectable } = useStore(selector, shallow);
- const setUserSelection = useStoreActions((actions) => actions.setUserSelection);
- const updateUserSelection = useStoreActions((actions) => actions.updateUserSelection);
- const unsetUserSelection = useStoreActions((actions) => actions.unsetUserSelection);
- const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
- const renderUserSelectionPane = selectionActive || selectionKeyPressed;
+ const renderUserSelectionPane = userSelectionActive || selectionKeyPressed;
+
+ const resetUserSelection = useCallback(() => {
+ setUserSelectionRect(initialRect);
+
+ store.setState({ userSelectionActive: false });
+
+ prevSelectedNodesCount.current = 0;
+ prevSelectedEdgesCount.current = 0;
+ }, []);
+
+ const onMouseDown = useCallback((event: React.MouseEvent): void => {
+ const reactFlowNode = (event.target as Element).closest('.react-flow')!;
+ containerBounds.current = reactFlowNode.getBoundingClientRect();
+
+ const mousePos = getMousePosition(event, containerBounds.current!);
+
+ setUserSelectionRect({
+ width: 0,
+ height: 0,
+ startX: mousePos.x,
+ startY: mousePos.y,
+ x: mousePos.x,
+ y: mousePos.y,
+ draw: true,
+ });
+
+ store.setState({ userSelectionActive: true, nodesSelectionActive: false });
+ }, []);
+
+ const onMouseMove = (event: React.MouseEvent): void => {
+ if (!selectionKeyPressed || !userSelectionRect.draw || !containerBounds.current) {
+ return;
+ }
+
+ const mousePos = getMousePosition(event, containerBounds.current!);
+ const startX = userSelectionRect.startX ?? 0;
+ const startY = userSelectionRect.startY ?? 0;
+
+ const nextUserSelectRect = {
+ ...userSelectionRect,
+ x: mousePos.x < startX ? mousePos.x : startX,
+ y: mousePos.y < startY ? mousePos.y : startY,
+ width: Math.abs(mousePos.x - startX),
+ height: Math.abs(mousePos.y - startY),
+ };
+
+ const { nodeInternals, edges, transform, onNodesChange, onEdgesChange } = store.getState();
+ const nodes = Array.from(nodeInternals).map(([_, node]) => node);
+ const selectedNodes = getNodesInside(nodeInternals, nextUserSelectRect, transform, false, true);
+ const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
+ const selectedNodeIds = selectedNodes.map((n) => n.id);
+
+ if (prevSelectedNodesCount.current !== selectedNodeIds.length) {
+ prevSelectedNodesCount.current = selectedNodeIds.length;
+ const changes = getSelectionChanges(nodes, selectedNodeIds) as NodeChange[];
+ if (changes.length) {
+ onNodesChange?.(changes);
+ }
+ }
+
+ if (prevSelectedEdgesCount.current !== selectedEdgeIds.length) {
+ prevSelectedEdgesCount.current = selectedEdgeIds.length;
+ const changes = getSelectionChanges(edges, selectedEdgeIds) as EdgeChange[];
+ if (changes.length) {
+ onEdgesChange?.(changes);
+ }
+ }
+
+ setUserSelectionRect(nextUserSelectRect);
+ };
+
+ const onMouseUp = useCallback(() => {
+ store.setState({ nodesSelectionActive: prevSelectedNodesCount.current > 0 });
+
+ resetUserSelection();
+ }, []);
+
+ const onMouseLeave = useCallback(() => {
+ store.setState({ nodesSelectionActive: false });
+
+ resetUserSelection();
+ }, []);
if (!elementsSelectable || !renderUserSelectionPane) {
return null;
}
- const onMouseDown = (event: React.MouseEvent): void => {
- const mousePos = getMousePosition(event);
- if (!mousePos) {
- return;
- }
-
- setUserSelection(mousePos);
- };
-
- const onMouseMove = (event: React.MouseEvent): void => {
- if (!selectionKeyPressed || !selectionActive) {
- return;
- }
- const mousePos = getMousePosition(event);
-
- if (!mousePos) {
- return;
- }
-
- updateUserSelection(mousePos);
- };
-
- const onMouseUp = () => unsetUserSelection();
-
- const onMouseLeave = () => {
- unsetUserSelection();
- unsetNodesSelection();
- };
-
return (
-
+ {userSelectionRect.draw && (
+
+ )}
);
});
diff --git a/src/container/EdgeRenderer/MarkerDefinitions.tsx b/src/container/EdgeRenderer/MarkerDefinitions.tsx
index 2fceee6c..dcae27bf 100644
--- a/src/container/EdgeRenderer/MarkerDefinitions.tsx
+++ b/src/container/EdgeRenderer/MarkerDefinitions.tsx
@@ -1,52 +1,81 @@
-import React, { ReactNode } from 'react';
+import React, { useMemo } from 'react';
-interface MarkerProps {
+import { useStore } from '../../store';
+import { EdgeMarker, ReactFlowState } from '../../types';
+import { getMarkerId } from '../../utils/graph';
+import { useMarkerSymbol } from './MarkerSymbols';
+interface MarkerProps extends EdgeMarker {
id: string;
- children: ReactNode;
}
-
-const Marker = ({ id, children }: MarkerProps) => (
-
- {children}
-
-);
-
interface MarkerDefinitionsProps {
- color: string;
+ defaultColor: string;
}
-const MarkerDefinitions = ({ color }: MarkerDefinitionsProps) => {
+const Marker = ({
+ id,
+ type,
+ color,
+ width = 12.5,
+ height = 12.5,
+ markerUnits = 'strokeWidth',
+ strokeWidth,
+ orient = 'auto',
+}: MarkerProps) => {
+ const Symbol = useMarkerSymbol(type);
+
+ return (
+
+
+
+ );
+};
+
+const edgesSelector = (s: ReactFlowState) => s.edges;
+
+const MarkerDefinitions = ({ defaultColor }: MarkerDefinitionsProps) => {
+ const edges = useStore(edgesSelector);
+ const markers = useMemo(() => {
+ const ids: string[] = [];
+
+ return edges.reduce((markers, edge) => {
+ [edge.markerStart, edge.markerEnd].forEach((marker) => {
+ if (marker && typeof marker === 'object') {
+ const markerId = getMarkerId(marker);
+ if (!ids.includes(markerId)) {
+ markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
+ ids.push(markerId);
+ }
+ }
+ });
+ return markers.sort((a, b) => a.id.localeCompare(b.id));
+ }, []);
+ }, [edges, defaultColor]);
+
return (
-
- (
+
-
-
-
-
+ ))}
);
};
diff --git a/src/container/EdgeRenderer/MarkerSymbols.tsx b/src/container/EdgeRenderer/MarkerSymbols.tsx
new file mode 100644
index 00000000..b9f47a81
--- /dev/null
+++ b/src/container/EdgeRenderer/MarkerSymbols.tsx
@@ -0,0 +1,51 @@
+import React, { useMemo } from 'react';
+import { MarkerType, EdgeMarker } from '../../types';
+
+type SymbolProps = Omit;
+
+const ArrowSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
+ return (
+
+ );
+};
+
+const ArrowClosedSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
+ return (
+
+ );
+};
+
+export const MarkerSymbols = {
+ [MarkerType.Arrow]: ArrowSymbol,
+ [MarkerType.ArrowClosed]: ArrowClosedSymbol,
+};
+
+export function useMarkerSymbol(type: MarkerType) {
+ const symbol = useMemo(() => {
+ const symbolExists = MarkerSymbols.hasOwnProperty(type);
+
+ if (!symbolExists) {
+ console.warn(`marker type "${type}" doesn't exist.`);
+ return () => null;
+ }
+
+ return MarkerSymbols[type];
+ }, [type]);
+ return symbol;
+}
+
+export default MarkerSymbols;
diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx
index 21d7034f..536f5005 100644
--- a/src/container/EdgeRenderer/index.tsx
+++ b/src/container/EdgeRenderer/index.tsx
@@ -1,33 +1,30 @@
-import React, { memo, CSSProperties, useCallback } from 'react';
+import React, { memo, CSSProperties } from 'react';
+import shallow from 'zustand/shallow';
+import cc from 'classcat';
-import { useStoreState } from '../../store/hooks';
+import { useStore } from '../../store';
import ConnectionLine from '../../components/ConnectionLine/index';
-import { isEdge } from '../../utils/graph';
import MarkerDefinitions from './MarkerDefinitions';
-import { getEdgePositions, getHandle, isEdgeVisible, getSourceTargetNodes } from './utils';
+import { getEdgePositions, getHandle, getNodeData } from './utils';
import {
Position,
Edge,
- Node,
- Elements,
- Connection,
ConnectionLineType,
ConnectionLineComponent,
ConnectionMode,
- Transform,
OnEdgeUpdateFunc,
+ ReactFlowState,
} from '../../types';
+import useVisibleEdges from '../../hooks/useVisibleEdges';
interface EdgeRendererProps {
edgeTypes: any;
connectionLineType: ConnectionLineType;
connectionLineStyle?: CSSProperties;
connectionLineComponent?: ConnectionLineComponent;
- connectionMode?: ConnectionMode;
- onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
+ onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
- arrowHeadColor: string;
- markerEndId?: string;
+ defaultMarkerColor: string;
onlyRenderVisibleElements: boolean;
onEdgeUpdate?: OnEdgeUpdateFunc;
onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
@@ -37,215 +34,155 @@ interface EdgeRendererProps {
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void;
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
edgeUpdaterRadius?: number;
+ noPanClassName?: string;
}
-interface EdgeWrapperProps {
- edge: Edge;
- props: EdgeRendererProps;
- nodes: Node[];
- selectedElements: Elements | null;
- elementsSelectable: boolean;
- transform: Transform;
- width: number;
- height: number;
- onlyRenderVisibleElements: boolean;
- connectionMode?: ConnectionMode;
-}
-
-const Edge = ({
- edge,
- props,
- nodes,
- selectedElements,
- elementsSelectable,
- transform,
- width,
- height,
- onlyRenderVisibleElements,
- connectionMode,
-}: EdgeWrapperProps) => {
- const sourceHandleId = edge.sourceHandle || null;
- const targetHandleId = edge.targetHandle || null;
- const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes);
-
- const onConnectEdge = useCallback(
- (connection: Connection) => {
- props.onEdgeUpdate?.(edge, connection);
- },
- [edge, props.onEdgeUpdate]
- );
-
- if (!sourceNode) {
- console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
- return null;
- }
-
- if (!targetNode) {
- console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`);
- return null;
- }
-
- // source and target node need to be initialized
- if (!sourceNode.__rf.width || !targetNode.__rf.width) {
- return null;
- }
-
- const edgeType = edge.type || 'default';
- const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
- const targetNodeBounds = targetNode.__rf.handleBounds;
- // when connection type is loose we can define all handles as sources
- const targetNodeHandles =
- connectionMode === ConnectionMode.Strict
- ? targetNodeBounds.target
- : targetNodeBounds.target || targetNodeBounds.source;
- const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId);
- const targetHandle = getHandle(targetNodeHandles, targetHandleId);
- const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom;
- const targetPosition = targetHandle ? targetHandle.position : Position.Top;
-
- if (!sourceHandle) {
- console.warn(`couldn't create edge for source handle id: ${sourceHandleId}; edge id: ${edge.id}`);
- return null;
- }
-
- if (!targetHandle) {
- console.warn(`couldn't create edge for target handle id: ${targetHandleId}; edge id: ${edge.id}`);
- return null;
- }
-
- const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
- sourceNode,
- sourceHandle,
- sourcePosition,
- targetNode,
- targetHandle,
- targetPosition
- );
-
- const isVisible = onlyRenderVisibleElements
- ? isEdgeVisible({
- sourcePos: { x: sourceX, y: sourceY },
- targetPos: { x: targetX, y: targetY },
- width,
- height,
- transform,
- })
- : true;
-
- if (!isVisible) {
- return null;
- }
-
- const isSelected = selectedElements?.some((elm) => isEdge(elm) && elm.id === edge.id) || false;
-
- return (
-
- );
-};
+const selector = (s: ReactFlowState) => ({
+ connectionNodeId: s.connectionNodeId,
+ connectionHandleId: s.connectionHandleId,
+ connectionHandleType: s.connectionHandleType,
+ connectionPosition: s.connectionPosition,
+ nodesConnectable: s.nodesConnectable,
+ elementsSelectable: s.elementsSelectable,
+ width: s.width,
+ height: s.height,
+ connectionMode: s.connectionMode,
+ nodeInternals: s.nodeInternals,
+});
const EdgeRenderer = (props: EdgeRendererProps) => {
- const transform = useStoreState((state) => state.transform);
- const nodes = useStoreState((state) => state.nodes);
- const edges = useStoreState((state) => state.edges);
- const connectionNodeId = useStoreState((state) => state.connectionNodeId);
- const connectionHandleId = useStoreState((state) => state.connectionHandleId);
- const connectionHandleType = useStoreState((state) => state.connectionHandleType);
- const connectionPosition = useStoreState((state) => state.connectionPosition);
- const selectedElements = useStoreState((state) => state.selectedElements);
- const nodesConnectable = useStoreState((state) => state.nodesConnectable);
- const elementsSelectable = useStoreState((state) => state.elementsSelectable);
- const width = useStoreState((state) => state.width);
- const height = useStoreState((state) => state.height);
+ const {
+ connectionNodeId,
+ connectionHandleId,
+ connectionHandleType,
+ connectionPosition,
+ nodesConnectable,
+ elementsSelectable,
+ width,
+ height,
+ connectionMode,
+ nodeInternals,
+ } = useStore(selector, shallow);
+ const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals);
if (!width) {
return null;
}
- const {
- connectionLineType,
- arrowHeadColor,
- connectionLineStyle,
- connectionLineComponent,
- onlyRenderVisibleElements,
- } = props;
- const transformStyle = `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`;
+ const { connectionLineType, defaultMarkerColor, connectionLineStyle, connectionLineComponent } = props;
const renderConnectionLine = connectionNodeId && connectionHandleType;
return (
-
-
-
- {edges.map((edge: Edge) => (
-
- ))}
- {renderConnectionLine && (
-
- )}
-
-
+ <>
+ {edgeTree.map(({ level, edges, isMaxLevel }) => (
+
+ {isMaxLevel && }
+
+ {edges.map((edge: Edge) => {
+ const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source);
+ const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals, edge.target);
+
+ if (!sourceIsValid || !targetIsValid) {
+ return null;
+ }
+
+ const edgeType = edge.type || 'default';
+ const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
+ // when connection type is loose we can define all handles as sources
+ const targetNodeHandles =
+ connectionMode === ConnectionMode.Strict
+ ? targetHandleBounds!.target
+ : targetHandleBounds!.target || targetHandleBounds!.source;
+ const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle || null);
+ const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle || null);
+ const sourcePosition = sourceHandle?.position || Position.Bottom;
+ const targetPosition = targetHandle?.position || Position.Top;
+
+ if (!sourceHandle) {
+ console.warn(`couldn't create edge for source handle id: ${edge.sourceHandle}; edge id: ${edge.id}`);
+ return null;
+ }
+
+ if (!targetHandle) {
+ console.warn(`couldn't create edge for target handle id: ${edge.targetHandle}; edge id: ${edge.id}`);
+ return null;
+ }
+
+ const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
+ sourceNodeRect,
+ sourceHandle,
+ sourcePosition,
+ targetNodeRect,
+ targetHandle,
+ targetPosition
+ );
+
+ return (
+
+ );
+ })}
+ {renderConnectionLine && isMaxLevel && (
+
+ )}
+
+
+ ))}
+ >
);
};
diff --git a/src/container/EdgeRenderer/utils.ts b/src/container/EdgeRenderer/utils.ts
index 0344aff8..450995e2 100644
--- a/src/container/EdgeRenderer/utils.ts
+++ b/src/container/EdgeRenderer/utils.ts
@@ -1,31 +1,32 @@
import { ComponentType } from 'react';
-
-import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
+import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges';
import wrapEdge from '../../components/Edges/wrapEdge';
-import { rectToBox } from '../../utils/graph';
-
import {
- EdgeTypesType,
EdgeProps,
- Position,
- Node,
- XYPosition,
- ElementId,
+ EdgeTypes,
HandleElement,
+ NodeHandleBounds,
+ NodeInternals,
+ Position,
+ Rect,
Transform,
- Edge,
+ XYPosition,
} from '../../types';
+import { rectToBox } from '../../utils';
-export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
- const standardTypes: EdgeTypesType = {
+export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypes;
+
+export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypes {
+ const standardTypes: EdgeTypes = {
default: wrapEdge((edgeTypes.default || BezierEdge) as ComponentType),
straight: wrapEdge((edgeTypes.bezier || StraightEdge) as ComponentType),
step: wrapEdge((edgeTypes.step || StepEdge) as ComponentType),
smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdge) as ComponentType),
+ simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdge) as ComponentType),
};
- const wrappedTypes = {} as EdgeTypesType;
- const specialTypes: EdgeTypesType = Object.keys(edgeTypes)
+ const wrappedTypes = {} as EdgeTypes;
+ const specialTypes: EdgeTypes = Object.keys(edgeTypes)
.filter((k) => !['default', 'bezier'].includes(k))
.reduce((res, key) => {
res[key] = wrapEdge((edgeTypes[key] || BezierEdge) as ComponentType);
@@ -39,11 +40,11 @@ export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
};
}
-export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
- const x = (handle?.x || 0) + node.__rf.position.x;
- const y = (handle?.y || 0) + node.__rf.position.y;
- const width = handle?.width || node.__rf.width;
- const height = handle?.height || node.__rf.height;
+export function getHandlePosition(position: Position, nodeRect: Rect, handle: any | null = null): XYPosition {
+ const x = (handle?.x || 0) + nodeRect.x;
+ const y = (handle?.y || 0) + nodeRect.y;
+ const width = handle?.width || nodeRect.width;
+ const height = handle?.height || nodeRect.height;
switch (position) {
case Position.Top:
@@ -69,7 +70,7 @@ export function getHandlePosition(position: Position, node: Node, handle: any |
}
}
-export function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleElement | null {
+export function getHandle(bounds: HandleElement[], handleId: string | null): HandleElement | null {
if (!bounds) {
return null;
}
@@ -94,15 +95,15 @@ interface EdgePositions {
}
export const getEdgePositions = (
- sourceNode: Node,
+ sourceNodeRect: Rect,
sourceHandle: HandleElement | unknown,
sourcePosition: Position,
- targetNode: Node,
+ targetNodeRect: Rect,
targetHandle: HandleElement | unknown,
targetPosition: Position
): EdgePositions => {
- const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
- const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
+ const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
+ const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
return {
sourceX: sourceHandlePos.x,
@@ -115,17 +116,31 @@ export const getEdgePositions = (
interface IsEdgeVisibleParams {
sourcePos: XYPosition;
targetPos: XYPosition;
+ sourceWidth: number;
+ sourceHeight: number;
+ targetWidth: number;
+ targetHeight: number;
width: number;
height: number;
transform: Transform;
}
-export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }: IsEdgeVisibleParams): boolean {
+export function isEdgeVisible({
+ sourcePos,
+ targetPos,
+ sourceWidth,
+ sourceHeight,
+ targetWidth,
+ targetHeight,
+ width,
+ height,
+ transform,
+}: IsEdgeVisibleParams): boolean {
const edgeBox = {
x: Math.min(sourcePos.x, targetPos.x),
y: Math.min(sourcePos.y, targetPos.y),
- x2: Math.max(sourcePos.x, targetPos.x),
- y2: Math.max(sourcePos.y, targetPos.y),
+ x2: Math.max(sourcePos.x + sourceWidth, targetPos.x + targetWidth),
+ y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight),
};
if (edgeBox.x === edgeBox.x2) {
@@ -150,22 +165,25 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
return overlappingArea > 0;
}
-type SourceTargetNode = {
- sourceNode: Node | null;
- targetNode: Node | null;
-};
+export function getNodeData(nodeInternals: NodeInternals, nodeId: string): [Rect, NodeHandleBounds | null, boolean] {
+ const node = nodeInternals.get(nodeId);
+ const handleBounds = node?.handleBounds;
+ const isInvalid =
+ !node ||
+ !node.handleBounds ||
+ !node.width ||
+ !node.height ||
+ typeof node.positionAbsolute?.x === 'undefined' ||
+ typeof node.positionAbsolute?.y === 'undefined';
-export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => {
- return nodes.reduce(
- (res, node) => {
- if (node.id === edge.source) {
- res.sourceNode = node;
- }
- if (node.id === edge.target) {
- res.targetNode = node;
- }
- return res;
+ return [
+ {
+ x: node?.positionAbsolute?.x || 0,
+ y: node?.positionAbsolute?.y || 0,
+ width: node?.width || 0,
+ height: node?.height || 0,
},
- { sourceNode: null, targetNode: null } as SourceTargetNode
- );
-};
+ handleBounds || null,
+ !isInvalid,
+ ];
+}
diff --git a/src/container/FlowRenderer/index.tsx b/src/container/FlowRenderer/index.tsx
index bc976a87..57b0ba11 100644
--- a/src/container/FlowRenderer/index.tsx
+++ b/src/container/FlowRenderer/index.tsx
@@ -1,18 +1,18 @@
import React, { useCallback, memo, ReactNode, WheelEvent, MouseEvent } from 'react';
-import { useStoreActions, useStoreState } from '../../store/hooks';
+import shallow from 'zustand/shallow';
+import { useStore, useStoreApi } from '../../store';
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
import useKeyPress from '../../hooks/useKeyPress';
-
import { GraphViewProps } from '../GraphView';
import ZoomPane from '../ZoomPane';
import UserSelection from '../../components/UserSelection';
import NodesSelection from '../../components/NodesSelection';
+import { ReactFlowState } from '../../types';
interface FlowRendererProps
extends Omit<
GraphViewProps,
- | 'elements'
| 'snapToGrid'
| 'nodeTypes'
| 'edgeTypes'
@@ -21,16 +21,21 @@ interface FlowRendererProps
| 'arrowHeadColor'
| 'onlyRenderVisibleElements'
| 'selectNodesOnDrag'
+ | 'defaultMarkerColor'
> {
children: ReactNode;
}
+const selector = (s: ReactFlowState) => ({
+ resetSelectedElements: s.resetSelectedElements,
+ nodesSelectionActive: s.nodesSelectionActive,
+});
+
const FlowRenderer = ({
children,
onPaneClick,
onPaneContextMenu,
onPaneScroll,
- onElementsRemove,
deleteKeyCode,
onMove,
onMoveStart,
@@ -45,46 +50,35 @@ const FlowRenderer = ({
panOnScrollSpeed,
panOnScrollMode,
zoomOnDoubleClick,
- paneMoveable,
+ panOnDrag,
defaultPosition,
defaultZoom,
- translateExtent,
preventScrolling,
onSelectionDragStart,
onSelectionDrag,
onSelectionDragStop,
onSelectionContextMenu,
+ noWheelClassName,
+ noPanClassName,
}: FlowRendererProps) => {
- const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
- const resetSelectedElements = useStoreActions((actions) => actions.resetSelectedElements);
- const nodesSelectionActive = useStoreState((state) => state.nodesSelectionActive);
-
+ const store = useStoreApi();
+ const { resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow);
const selectionKeyPressed = useKeyPress(selectionKeyCode);
- useGlobalKeyHandler({ onElementsRemove, deleteKeyCode, multiSelectionKeyCode });
+ useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
const onClick = useCallback(
(event: MouseEvent) => {
onPaneClick?.(event);
- unsetNodesSelection();
resetSelectedElements();
+
+ store.setState({ nodesSelectionActive: false });
},
[onPaneClick]
);
- const onContextMenu = useCallback(
- (event: MouseEvent) => {
- onPaneContextMenu?.(event);
- },
- [onPaneContextMenu]
- );
-
- const onWheel = useCallback(
- (event: WheelEvent) => {
- onPaneScroll?.(event);
- },
- [onPaneScroll]
- );
+ const onContextMenu = useCallback((event: MouseEvent) => onPaneContextMenu?.(event), [onPaneContextMenu]);
+ const onWheel = useCallback((event: WheelEvent) => onPaneScroll?.(event), [onPaneScroll]);
return (
{children}
@@ -114,9 +109,15 @@ const FlowRenderer = ({
onSelectionDrag={onSelectionDrag}
onSelectionDragStop={onSelectionDragStop}
onSelectionContextMenu={onSelectionContextMenu}
+ noPanClassName={noPanClassName}
/>
)}
-
+
);
};
diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx
index e002f366..8d3c2107 100644
--- a/src/container/GraphView/index.tsx
+++ b/src/container/GraphView/index.tsx
@@ -1,30 +1,27 @@
-import React, { useEffect, useRef, memo } from 'react';
+import React, { memo } from 'react';
-import { useStoreActions, useStore } from '../../store/hooks';
import FlowRenderer from '../FlowRenderer';
import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer';
-import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph';
-import useZoomPanHelper from '../../hooks/useZoomPanHelper';
+import Viewport from '../Viewport';
+import useOnInitHandler from '../../hooks/useOnInitHandler';
+import { NodeTypes, EdgeTypes, ConnectionLineType, KeyCode, ReactFlowProps } from '../../types';
-import { ReactFlowProps } from '../ReactFlow';
-
-import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
-
-export interface GraphViewProps extends Omit {
- nodeTypes: NodeTypesType;
- edgeTypes: EdgeTypesType;
- selectionKeyCode: KeyCode;
- deleteKeyCode: KeyCode;
- multiSelectionKeyCode: KeyCode;
+export interface GraphViewProps extends Omit {
+ nodeTypes: NodeTypes;
+ edgeTypes: EdgeTypes;
+ selectionKeyCode: KeyCode | null;
+ deleteKeyCode: KeyCode | null;
+ multiSelectionKeyCode: KeyCode | null;
connectionLineType: ConnectionLineType;
- snapToGrid: boolean;
- snapGrid: [number, number];
onlyRenderVisibleElements: boolean;
defaultZoom: number;
defaultPosition: [number, number];
- arrowHeadColor: string;
+ defaultMarkerColor: string;
selectNodesOnDrag: boolean;
+ noDragClassName: string;
+ noWheelClassName: string;
+ noPanClassName: string;
}
const GraphView = ({
@@ -33,8 +30,9 @@ const GraphView = ({
onMove,
onMoveStart,
onMoveEnd,
- onLoad,
- onElementClick,
+ onInit,
+ onNodeClick,
+ onEdgeClick,
onNodeDoubleClick,
onEdgeDoubleClick,
onNodeMouseEnter,
@@ -48,42 +46,27 @@ const GraphView = ({
onSelectionDrag,
onSelectionDragStop,
onSelectionContextMenu,
- connectionMode,
connectionLineType,
connectionLineStyle,
connectionLineComponent,
selectionKeyCode,
multiSelectionKeyCode,
zoomActivationKeyCode,
- onElementsRemove,
deleteKeyCode,
- onConnect,
- onConnectStart,
- onConnectStop,
- onConnectEnd,
- snapToGrid,
- snapGrid,
onlyRenderVisibleElements,
- nodesDraggable,
- nodesConnectable,
elementsSelectable,
selectNodesOnDrag,
- minZoom,
- maxZoom,
defaultZoom,
defaultPosition,
- translateExtent,
preventScrolling,
- nodeExtent,
- arrowHeadColor,
- markerEndId,
+ defaultMarkerColor,
zoomOnScroll,
zoomOnPinch,
panOnScroll,
panOnScrollSpeed,
panOnScrollMode,
zoomOnDoubleClick,
- paneMoveable,
+ panOnDrag,
onPaneClick,
onPaneScroll,
onPaneContextMenu,
@@ -95,134 +78,17 @@ const GraphView = ({
edgeUpdaterRadius,
onEdgeUpdateStart,
onEdgeUpdateEnd,
+ noDragClassName,
+ noWheelClassName,
+ noPanClassName,
}: GraphViewProps) => {
- const isInitialized = useRef(false);
- const setOnConnect = useStoreActions((actions) => actions.setOnConnect);
- const setOnConnectStart = useStoreActions((actions) => actions.setOnConnectStart);
- const setOnConnectStop = useStoreActions((actions) => actions.setOnConnectStop);
- const setOnConnectEnd = useStoreActions((actions) => actions.setOnConnectEnd);
- const setSnapGrid = useStoreActions((actions) => actions.setSnapGrid);
- const setSnapToGrid = useStoreActions((actions) => actions.setSnapToGrid);
- const setNodesDraggable = useStoreActions((actions) => actions.setNodesDraggable);
- const setNodesConnectable = useStoreActions((actions) => actions.setNodesConnectable);
- const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable);
- const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
- const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
- const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
- const setNodeExtent = useStoreActions((actions) => actions.setNodeExtent);
- const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
- const currentStore = useStore();
- const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
-
- useEffect(() => {
- if (!isInitialized.current && initialized) {
- if (onLoad) {
- onLoad({
- fitView: (params = { padding: 0.1 }) => fitView(params),
- zoomIn,
- zoomOut,
- zoomTo,
- setTransform: transform,
- project: onLoadProject(currentStore),
- getElements: onLoadGetElements(currentStore),
- toObject: onLoadToObject(currentStore),
- });
- }
-
- isInitialized.current = true;
- }
- }, [onLoad, zoomIn, zoomOut, zoomTo, transform, fitView, initialized]);
-
- useEffect(() => {
- if (onConnect) {
- setOnConnect(onConnect);
- }
- }, [onConnect]);
-
- useEffect(() => {
- if (onConnectStart) {
- setOnConnectStart(onConnectStart);
- }
- }, [onConnectStart]);
-
- useEffect(() => {
- if (onConnectStop) {
- setOnConnectStop(onConnectStop);
- }
- }, [onConnectStop]);
-
- useEffect(() => {
- if (onConnectEnd) {
- setOnConnectEnd(onConnectEnd);
- }
- }, [onConnectEnd]);
-
- useEffect(() => {
- if (typeof snapToGrid !== 'undefined') {
- setSnapToGrid(snapToGrid);
- }
- }, [snapToGrid]);
-
- useEffect(() => {
- if (typeof snapGrid !== 'undefined') {
- setSnapGrid(snapGrid);
- }
- }, [snapGrid]);
-
- useEffect(() => {
- if (typeof nodesDraggable !== 'undefined') {
- setNodesDraggable(nodesDraggable);
- }
- }, [nodesDraggable]);
-
- useEffect(() => {
- if (typeof nodesConnectable !== 'undefined') {
- setNodesConnectable(nodesConnectable);
- }
- }, [nodesConnectable]);
-
- useEffect(() => {
- if (typeof elementsSelectable !== 'undefined') {
- setElementsSelectable(elementsSelectable);
- }
- }, [elementsSelectable]);
-
- useEffect(() => {
- if (typeof minZoom !== 'undefined') {
- setMinZoom(minZoom);
- }
- }, [minZoom]);
-
- useEffect(() => {
- if (typeof maxZoom !== 'undefined') {
- setMaxZoom(maxZoom);
- }
- }, [maxZoom]);
-
- useEffect(() => {
- if (typeof translateExtent !== 'undefined') {
- setTranslateExtent(translateExtent);
- }
- }, [translateExtent]);
-
- useEffect(() => {
- if (typeof nodeExtent !== 'undefined') {
- setNodeExtent(nodeExtent);
- }
- }, [nodeExtent]);
-
- useEffect(() => {
- if (typeof connectionMode !== 'undefined') {
- setConnectionMode(connectionMode);
- }
- }, [connectionMode]);
+ useOnInitHandler(onInit);
return (
-
-
+
+
+
+
);
};
diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx
index 8b10d69a..663458d3 100644
--- a/src/container/NodeRenderer/index.tsx
+++ b/src/container/NodeRenderer/index.tsx
@@ -1,12 +1,14 @@
-import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
+import React, { memo, useMemo, ComponentType, MouseEvent, useEffect, useRef } from 'react';
+import shallow from 'zustand/shallow';
+
+import useVisibleNodes from '../../hooks/useVisibleNodes';
+import { useStore } from '../../store';
+import { Node, NodeTypes, Position, ReactFlowState, WrapNodeProps } from '../../types';
-import { getNodesInside } from '../../utils/graph';
-import { useStoreState, useStoreActions } from '../../store/hooks';
-import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
interface NodeRendererProps {
- nodeTypes: NodeTypesType;
+ nodeTypes: NodeTypes;
selectNodesOnDrag: boolean;
- onElementClick?: (event: MouseEvent, element: Node | Edge) => void;
+ onNodeClick?: (event: MouseEvent, element: Node) => void;
onNodeDoubleClick?: (event: MouseEvent, element: Node) => void;
onNodeMouseEnter?: (event: MouseEvent, node: Node) => void;
onNodeMouseMove?: (event: MouseEvent, node: Node) => void;
@@ -15,58 +17,64 @@ interface NodeRendererProps {
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
onNodeDrag?: (event: MouseEvent, node: Node) => void;
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
- snapToGrid: boolean;
- snapGrid: [number, number];
onlyRenderVisibleElements: boolean;
+ noPanClassName: string;
+ noDragClassName: string;
}
+const selector = (s: ReactFlowState) => ({
+ scale: s.transform[2],
+ nodesDraggable: s.nodesDraggable,
+ nodesConnectable: s.nodesConnectable,
+ elementsSelectable: s.elementsSelectable,
+ updateNodeDimensions: s.updateNodeDimensions,
+ snapGrid: s.snapGrid,
+ snapToGrid: s.snapToGrid,
+ nodeInternals: s.nodeInternals,
+});
+
const NodeRenderer = (props: NodeRendererProps) => {
- const transform = useStoreState((state) => state.transform);
- const selectedElements = useStoreState((state) => state.selectedElements);
- const nodesDraggable = useStoreState((state) => state.nodesDraggable);
- const nodesConnectable = useStoreState((state) => state.nodesConnectable);
- const elementsSelectable = useStoreState((state) => state.elementsSelectable);
- const width = useStoreState((state) => state.width);
- const height = useStoreState((state) => state.height);
- const nodes = useStoreState((state) => state.nodes);
- const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
-
- const visibleNodes = props.onlyRenderVisibleElements
- ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
- : nodes;
-
- const transformStyle = useMemo(
- () => ({
- transform: `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`,
- }),
- [transform[0], transform[1], transform[2]]
- );
+ const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } =
+ useStore(selector, shallow);
+ const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
+ const resizeObserverRef = useRef();
const resizeObserver = useMemo(() => {
if (typeof ResizeObserver === 'undefined') {
return null;
}
- return new ResizeObserver((entries: ResizeObserverEntry[]) => {
+ const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = entries.map((entry: ResizeObserverEntry) => ({
id: entry.target.getAttribute('data-id') as string,
nodeElement: entry.target as HTMLDivElement,
+ forceUpdate: true,
}));
updateNodeDimensions(updates);
});
+
+ resizeObserverRef.current = observer;
+
+ return observer;
+ }, []);
+
+ useEffect(() => {
+ return () => {
+ resizeObserverRef?.current?.disconnect();
+ };
}, []);
return (
-
- {visibleNodes.map((node) => {
+
+ {nodes.map((node) => {
const nodeType = node.type || 'default';
- const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType
;
if (!props.nodeTypes[nodeType]) {
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`);
}
+ const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType;
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'));
@@ -79,17 +87,16 @@ const NodeRenderer = (props: NodeRendererProps) => {
style={node.style}
type={nodeType}
data={node.data}
- sourcePosition={node.sourcePosition}
- targetPosition={node.targetPosition}
- isHidden={node.isHidden}
- xPos={node.__rf.position.x}
- yPos={node.__rf.position.y}
- isDragging={node.__rf.isDragging}
- isInitialized={node.__rf.width !== null && node.__rf.height !== null}
- snapGrid={props.snapGrid}
- snapToGrid={props.snapToGrid}
+ sourcePosition={node.sourcePosition || Position.Bottom}
+ targetPosition={node.targetPosition || Position.Top}
+ hidden={node.hidden}
+ xPos={node.positionAbsolute?.x ?? 0}
+ yPos={node.positionAbsolute?.y ?? 0}
+ dragging={!!node.dragging}
+ snapGrid={snapGrid}
+ snapToGrid={snapToGrid}
selectNodesOnDrag={props.selectNodesOnDrag}
- onClick={props.onElementClick}
+ onClick={props.onNodeClick}
onMouseEnter={props.onNodeMouseEnter}
onMouseMove={props.onNodeMouseMove}
onMouseLeave={props.onNodeMouseLeave}
@@ -98,13 +105,17 @@ const NodeRenderer = (props: NodeRendererProps) => {
onNodeDragStart={props.onNodeDragStart}
onNodeDrag={props.onNodeDrag}
onNodeDragStop={props.onNodeDragStop}
- scale={transform[2]}
- selected={selectedElements?.some(({ id }) => id === node.id) || false}
+ scale={scale}
+ selected={!!node.selected}
isDraggable={isDraggable}
isSelectable={isSelectable}
isConnectable={isConnectable}
resizeObserver={resizeObserver}
dragHandle={node.dragHandle}
+ zIndex={node.z ?? 0}
+ isParent={!!node.isParent}
+ noDragClassName={props.noDragClassName}
+ noPanClassName={props.noPanClassName}
/>
);
})}
diff --git a/src/container/NodeRenderer/utils.ts b/src/container/NodeRenderer/utils.ts
index 9af887a9..c656d74a 100644
--- a/src/container/NodeRenderer/utils.ts
+++ b/src/container/NodeRenderer/utils.ts
@@ -3,29 +3,25 @@ import { 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 { NodeTypesType, NodeComponentProps } from '../../types';
+import { NodeTypes, NodeProps } from '../../types';
-export function createNodeTypes(nodeTypes: NodeTypesType): NodeTypesType {
- const standardTypes: NodeTypesType = {
- input: wrapNode((nodeTypes.input || InputNode) as ComponentType<
- NodeComponentProps
- >),
- default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType<
- NodeComponentProps
- >),
- output: wrapNode((nodeTypes.output || OutputNode) as ComponentType<
- NodeComponentProps
- >),
+export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypes;
+
+export function createNodeTypes(nodeTypes: NodeTypes): NodeTypes {
+ const standardTypes: NodeTypes = {
+ input: wrapNode((nodeTypes.input || InputNode) as ComponentType),
+ default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType),
+ output: wrapNode((nodeTypes.output || OutputNode) as ComponentType),
+ group: wrapNode((nodeTypes.group || GroupNode) as ComponentType),
};
- const wrappedTypes = {} as NodeTypesType;
- const specialTypes: NodeTypesType = Object.keys(nodeTypes)
- .filter(k => !['input', 'default', 'output'].includes(k))
+ const wrappedTypes = {} as NodeTypes;
+ const specialTypes: NodeTypes = Object.keys(nodeTypes)
+ .filter((k) => !['input', 'default', 'output', 'group'].includes(k))
.reduce((res, key) => {
- res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType<
- NodeComponentProps
- >);
+ res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType);
return res;
}, wrappedTypes);
diff --git a/src/container/ReactFlow/Wrapper.tsx b/src/container/ReactFlow/Wrapper.tsx
index 4a41a47c..43aac450 100644
--- a/src/container/ReactFlow/Wrapper.tsx
+++ b/src/container/ReactFlow/Wrapper.tsx
@@ -1,21 +1,23 @@
-import React, { FC, useContext, useMemo } from 'react';
-import { Provider, ReactReduxContext } from 'react-redux';
+import React, { FC } from 'react';
-import store from '../../store';
+import { Provider, createStore, useStoreApi } from '../../store';
const Wrapper: FC = ({ children }) => {
- const contextValue = useContext(ReactReduxContext);
- const isWrappedWithReactFlowProvider = useMemo(() => contextValue?.store?.getState()?.reactFlowVersion, [
- contextValue,
- ]);
+ let isWrapped = true;
- if (isWrappedWithReactFlowProvider) {
+ try {
+ useStoreApi();
+ } catch (e) {
+ isWrapped = false;
+ }
+
+ if (isWrapped) {
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
return <>{children}>;
}
- return {children} ;
+ return {children} ;
};
Wrapper.displayName = 'ReactFlowWrapper';
diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx
index 66dbe234..0a133393 100644
--- a/src/container/ReactFlow/index.tsx
+++ b/src/container/ReactFlow/index.tsx
@@ -1,47 +1,33 @@
-import React, {
- useMemo,
- CSSProperties,
- HTMLAttributes,
- MouseEvent as ReactMouseEvent,
- WheelEvent,
- forwardRef,
-} from 'react';
import cc from 'classcat';
-
-import GraphView from '../GraphView';
-import ElementUpdater from '../../components/ElementUpdater';
+import React, { forwardRef } from 'react';
+import Attribution from '../../components/Attribution';
+import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges';
import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
-import { createNodeTypes } from '../NodeRenderer/utils';
import SelectionListener from '../../components/SelectionListener';
-import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
-import { createEdgeTypes } from '../EdgeRenderer/utils';
-import Wrapper from './Wrapper';
+import StoreUpdater from '../../components/StoreUpdater';
+import css from '../../style.css';
+import theme from '../../theme-default.css';
import {
- Elements,
- NodeTypesType,
- EdgeTypesType,
- OnLoadFunc,
- Node,
- Edge,
- Connection,
- ConnectionMode,
ConnectionLineType,
- ConnectionLineComponent,
- FlowTransform,
- OnConnectStartFunc,
- OnConnectStopFunc,
- OnConnectEndFunc,
- TranslateExtent,
- KeyCode,
+ ConnectionMode,
+ EdgeTypes,
+ NodeTypes,
PanOnScrollMode,
- OnEdgeUpdateFunc,
- NodeExtent,
+ ReactFlowProps,
+ ReactFlowRefType,
} from '../../types';
+import { createEdgeTypes } from '../EdgeRenderer/utils';
+import GraphView from '../GraphView';
+import { createNodeTypes } from '../NodeRenderer/utils';
+import injectStyle, { useNodeOrEdgeTypes } from './utils';
+import Wrapper from './Wrapper';
-import '../../style.css';
-import '../../theme-default.css';
+if (__INJECT_STYLES__) {
+ injectStyle(css as unknown as string);
+ injectStyle(theme as unknown as string);
+}
const defaultNodeTypes = {
input: InputNode,
@@ -54,97 +40,28 @@ const defaultEdgeTypes = {
straight: StraightEdge,
step: StepEdge,
smoothstep: SmoothStepEdge,
+ simplebezier: SimpleBezierEdge,
};
-export interface ReactFlowProps extends Omit, 'onLoad'> {
- elements: Elements;
- onElementClick?: (event: ReactMouseEvent, element: Node | Edge) => void;
- onElementsRemove?: (elements: Elements) => void;
- onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
- onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
- onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
- onNodeMouseLeave?: (event: ReactMouseEvent, node: Node) => void;
- onNodeContextMenu?: (event: ReactMouseEvent, node: Node) => void;
- onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
- onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
- onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
- onConnect?: (connection: Edge | Connection) => void;
- onConnectStart?: OnConnectStartFunc;
- onConnectStop?: OnConnectStopFunc;
- onConnectEnd?: OnConnectEndFunc;
- onLoad?: OnLoadFunc;
- onMove?: (flowTransform?: FlowTransform) => void;
- onMoveStart?: (flowTransform?: FlowTransform) => void;
- onMoveEnd?: (flowTransform?: FlowTransform) => void;
- onSelectionChange?: (elements: Elements | null) => void;
- onSelectionDragStart?: (event: ReactMouseEvent, nodes: Node[]) => void;
- onSelectionDrag?: (event: ReactMouseEvent, nodes: Node[]) => void;
- onSelectionDragStop?: (event: ReactMouseEvent, nodes: Node[]) => void;
- onSelectionContextMenu?: (event: ReactMouseEvent, nodes: Node[]) => void;
- onPaneScroll?: (event?: WheelEvent) => void;
- onPaneClick?: (event: ReactMouseEvent) => void;
- onPaneContextMenu?: (event: ReactMouseEvent) => void;
- nodeTypes?: NodeTypesType;
- edgeTypes?: EdgeTypesType;
- connectionMode?: ConnectionMode;
- connectionLineType?: ConnectionLineType;
- connectionLineStyle?: CSSProperties;
- connectionLineComponent?: ConnectionLineComponent;
- deleteKeyCode?: KeyCode;
- selectionKeyCode?: KeyCode;
- multiSelectionKeyCode?: KeyCode;
- zoomActivationKeyCode?: KeyCode;
- snapToGrid?: boolean;
- snapGrid?: [number, number];
- onlyRenderVisibleElements?: boolean;
- nodesDraggable?: boolean;
- nodesConnectable?: boolean;
- elementsSelectable?: boolean;
- selectNodesOnDrag?: boolean;
- paneMoveable?: boolean;
- minZoom?: number;
- maxZoom?: number;
- defaultZoom?: number;
- defaultPosition?: [number, number];
- translateExtent?: TranslateExtent;
- preventScrolling?: boolean;
- nodeExtent?: NodeExtent;
- arrowHeadColor?: string;
- markerEndId?: string;
- zoomOnScroll?: boolean;
- zoomOnPinch?: boolean;
- panOnScroll?: boolean;
- 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) => void;
- onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
- edgeUpdaterRadius?: number;
- nodeTypesId?: string;
- edgeTypesId?: string;
-}
-
-export type ReactFlowRefType = HTMLDivElement;
+const initSnapGrid: [number, number] = [15, 15];
+const initDefaultPosition: [number, number] = [0, 0];
const ReactFlow = forwardRef(
(
{
- elements = [],
+ nodes,
+ edges,
+ defaultNodes,
+ defaultEdges,
className,
nodeTypes = defaultNodeTypes,
edgeTypes = defaultEdgeTypes,
- onElementClick,
- onLoad,
+ onNodeClick,
+ onEdgeClick,
+ onInit,
onMove,
onMoveStart,
onMoveEnd,
- onElementsRemove,
onConnect,
onConnectStart,
onConnectStop,
@@ -157,6 +74,8 @@ const ReactFlow = forwardRef(
onNodeDragStart,
onNodeDrag,
onNodeDragStop,
+ onNodesDelete,
+ onEdgesDelete,
onSelectionChange,
onSelectionDragStart,
onSelectionDrag,
@@ -171,7 +90,7 @@ const ReactFlow = forwardRef(
multiSelectionKeyCode = 'Meta',
zoomActivationKeyCode = 'Meta',
snapToGrid = false,
- snapGrid = [15, 15],
+ snapGrid = initSnapGrid,
onlyRenderVisibleElements = false,
selectNodesOnDrag = true,
nodesDraggable,
@@ -180,19 +99,18 @@ const ReactFlow = forwardRef(
minZoom,
maxZoom,
defaultZoom = 1,
- defaultPosition = [0, 0],
+ defaultPosition = initDefaultPosition,
translateExtent,
preventScrolling = true,
nodeExtent,
- arrowHeadColor = '#b1b1b7',
- markerEndId,
+ defaultMarkerColor = '#b1b1b7',
zoomOnScroll = true,
zoomOnPinch = true,
panOnScroll = false,
panOnScrollSpeed = 0.5,
panOnScrollMode = PanOnScrollMode.Free,
zoomOnDoubleClick = true,
- paneMoveable = true,
+ panOnDrag = true,
onPaneClick,
onPaneScroll,
onPaneContextMenu,
@@ -206,25 +124,35 @@ const ReactFlow = forwardRef(
onEdgeUpdateStart,
onEdgeUpdateEnd,
edgeUpdaterRadius = 10,
- nodeTypesId = '1',
- edgeTypesId = '1',
+ onNodesChange,
+ onEdgesChange,
+ noDragClassName = 'nodrag',
+ noWheelClassName = 'nowheel',
+ noPanClassName = 'nopan',
+ fitView = false,
+ fitViewOptions,
+ connectOnClick = true,
+ attributionPosition,
+ proOptions,
+ defaultEdgeOptions,
...rest
},
ref
) => {
- const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), [nodeTypesId]);
- const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), [edgeTypesId]);
+ const nodeTypesParsed = useNodeOrEdgeTypes(nodeTypes, createNodeTypes) as NodeTypes;
+ const edgeTypesParsed = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes) as EdgeTypes;
const reactFlowClasses = cc(['react-flow', className]);
return (
(
onNodeDragStop={onNodeDragStop}
nodeTypes={nodeTypesParsed}
edgeTypes={edgeTypesParsed}
- connectionMode={connectionMode}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
connectionLineComponent={connectionLineComponent}
selectionKeyCode={selectionKeyCode}
- onElementsRemove={onElementsRemove}
deleteKeyCode={deleteKeyCode}
multiSelectionKeyCode={multiSelectionKeyCode}
zoomActivationKeyCode={zoomActivationKeyCode}
- onConnect={onConnect}
- onConnectStart={onConnectStart}
- onConnectStop={onConnectStop}
- onConnectEnd={onConnectEnd}
- snapToGrid={snapToGrid}
- snapGrid={snapGrid}
onlyRenderVisibleElements={onlyRenderVisibleElements}
- nodesDraggable={nodesDraggable}
- nodesConnectable={nodesConnectable}
- elementsSelectable={elementsSelectable}
selectNodesOnDrag={selectNodesOnDrag}
- minZoom={minZoom}
- maxZoom={maxZoom}
defaultZoom={defaultZoom}
defaultPosition={defaultPosition}
- translateExtent={translateExtent}
preventScrolling={preventScrolling}
- nodeExtent={nodeExtent}
- arrowHeadColor={arrowHeadColor}
- markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll}
zoomOnPinch={zoomOnPinch}
zoomOnDoubleClick={zoomOnDoubleClick}
panOnScroll={panOnScroll}
panOnScrollSpeed={panOnScrollSpeed}
panOnScrollMode={panOnScrollMode}
- paneMoveable={paneMoveable}
+ panOnDrag={panOnDrag}
onPaneClick={onPaneClick}
onPaneScroll={onPaneScroll}
onPaneContextMenu={onPaneContextMenu}
@@ -287,10 +198,42 @@ const ReactFlow = forwardRef(
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
edgeUpdaterRadius={edgeUpdaterRadius}
+ defaultMarkerColor={defaultMarkerColor}
+ noDragClassName={noDragClassName}
+ noWheelClassName={noWheelClassName}
+ noPanClassName={noPanClassName}
+ />
+
-
{onSelectionChange && }
{children}
+
);
diff --git a/src/container/ReactFlow/utils.ts b/src/container/ReactFlow/utils.ts
new file mode 100644
index 00000000..d431a60d
--- /dev/null
+++ b/src/container/ReactFlow/utils.ts
@@ -0,0 +1,39 @@
+import { useMemo, useRef } from 'react';
+import shallow from 'zustand/shallow';
+
+import { EdgeTypes, NodeTypes } from '../../types';
+import { CreateEdgeTypes } from '../EdgeRenderer/utils';
+import { CreateNodeTypes } from '../NodeRenderer/utils';
+
+export function useNodeOrEdgeTypes(nodeOrEdgeTypes: NodeTypes, createTypes: CreateNodeTypes): NodeTypes;
+export function useNodeOrEdgeTypes(nodeOrEdgeTypes: EdgeTypes, createTypes: CreateEdgeTypes): EdgeTypes;
+export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any {
+ const typesKeysRef = useRef(null);
+
+ const typesParsed = useMemo(() => {
+ if (process.env.NODE_ENV === 'development') {
+ const typeKeys = Object.keys(nodeOrEdgeTypes);
+ if (shallow(typesKeysRef.current, typeKeys)) {
+ console.warn(
+ "React Flow: It looks like that you created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them."
+ );
+ }
+
+ typesKeysRef.current = typeKeys;
+ }
+ return createTypes(nodeOrEdgeTypes);
+ }, [nodeOrEdgeTypes]);
+
+ return typesParsed;
+}
+
+export default function injectStyle(css: string): void {
+ if (!css || typeof document === 'undefined') return;
+
+ const head = document.head || document.getElementsByTagName('head')[0];
+ const style = document.createElement('style');
+
+ head.appendChild(style);
+
+ style.appendChild(document.createTextNode(css));
+}
diff --git a/src/container/Viewport/index.tsx b/src/container/Viewport/index.tsx
new file mode 100644
index 00000000..590b7516
--- /dev/null
+++ b/src/container/Viewport/index.tsx
@@ -0,0 +1,25 @@
+import React, { ReactNode } from 'react';
+
+import { useStore } from '../../store';
+import { ReactFlowState } from '../../types';
+
+const selector = (s: ReactFlowState) => s.transform;
+
+type ViewportProps = {
+ children: ReactNode;
+};
+
+function Viewport({ children }: ViewportProps) {
+ const transform = useStore(selector);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default Viewport;
diff --git a/src/container/ZoomPane/index.tsx b/src/container/ZoomPane/index.tsx
index 5af2659b..9f17226f 100644
--- a/src/container/ZoomPane/index.tsx
+++ b/src/container/ZoomPane/index.tsx
@@ -1,12 +1,13 @@
import React, { useEffect, useRef, ReactNode } from 'react';
-import { zoom, zoomIdentity } from 'd3-zoom';
+import { D3ZoomEvent, zoom, zoomIdentity } from 'd3-zoom';
import { select, pointer } from 'd3-selection';
+import shallow from 'zustand/shallow';
import { clamp } from '../../utils';
import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler';
-import { useStoreState, useStoreActions, useStore } from '../../store/hooks';
-import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode } from '../../types';
+import { useStore, useStoreApi } from '../../store';
+import { Viewport, PanOnScrollMode, KeyCode, ReactFlowState, OnMove, OnMoveStart, OnMoveEnd } from '../../types';
interface ZoomPaneProps {
selectionKeyPressed: boolean;
@@ -17,30 +18,35 @@ interface ZoomPaneProps {
panOnScrollSpeed?: number;
panOnScrollMode?: PanOnScrollMode;
zoomOnDoubleClick?: boolean;
- paneMoveable?: boolean;
+ panOnDrag?: boolean;
defaultPosition?: [number, number];
defaultZoom?: number;
- translateExtent?: TranslateExtent;
- onMove?: (flowTransform?: FlowTransform) => void;
- onMoveStart?: (flowTransform?: FlowTransform) => void;
- onMoveEnd?: (flowTransform?: FlowTransform) => void;
+ onMove?: OnMove;
+ onMoveStart?: OnMoveStart;
+ onMoveEnd?: OnMoveEnd;
zoomActivationKeyCode?: KeyCode;
preventScrolling?: boolean;
children: ReactNode;
+ noWheelClassName: string;
+ noPanClassName: string;
}
-const viewChanged = (prevTransform: FlowTransform, eventTransform: any): boolean =>
- prevTransform.x !== eventTransform.x ||
- prevTransform.y !== eventTransform.y ||
- prevTransform.zoom !== eventTransform.k;
+const viewChanged = (prevViewport: Viewport, eventViewport: any): boolean =>
+ prevViewport.x !== eventViewport.x || prevViewport.y !== eventViewport.y || prevViewport.zoom !== eventViewport.k;
-const eventToFlowTransform = (eventTransform: any): FlowTransform => ({
- x: eventTransform.x,
- y: eventTransform.y,
- zoom: eventTransform.k,
+const eventToFlowTransform = (eventViewport: any): Viewport => ({
+ x: eventViewport.x,
+ y: eventViewport.y,
+ zoom: eventViewport.k,
});
-const hasNoWheelClass = (event: any) => event.target.closest('.nowheel');
+const isWrappedWithClass = (event: any, className: string | undefined) => event.target.closest(`.${className}`);
+
+const selector = (s: ReactFlowState) => ({
+ d3Zoom: s.d3Zoom,
+ d3Selection: s.d3Selection,
+ d3ZoomHandler: s.d3ZoomHandler,
+});
const ZoomPane = ({
onMove,
@@ -54,44 +60,37 @@ const ZoomPane = ({
zoomOnDoubleClick = true,
selectionKeyPressed,
elementsSelectable,
- paneMoveable = true,
+ panOnDrag = true,
defaultPosition = [0, 0],
defaultZoom = 1,
- translateExtent,
zoomActivationKeyCode,
preventScrolling = true,
children,
+ noWheelClassName,
+ noPanClassName,
}: ZoomPaneProps) => {
+ const store = useStoreApi();
const zoomPane = useRef(null);
- const prevTransform = useRef({ x: 0, y: 0, zoom: 0 });
-
- const store = useStore();
- const d3Zoom = useStoreState((s) => s.d3Zoom);
- const d3Selection = useStoreState((s) => s.d3Selection);
- const d3ZoomHandler = useStoreState((s) => s.d3ZoomHandler);
-
- const initD3Zoom = useStoreActions((actions) => actions.initD3Zoom);
- const updateTransform = useStoreActions((actions) => actions.updateTransform);
-
+ const prevTransform = useRef({ x: 0, y: 0, zoom: 0 });
+ const { d3Zoom, d3Selection, d3ZoomHandler } = useStore(selector, shallow);
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
useResizeHandler(zoomPane);
useEffect(() => {
if (zoomPane.current) {
- const state = store.getState();
- const currentTranslateExtent = typeof translateExtent !== 'undefined' ? translateExtent : state.translateExtent;
- const d3ZoomInstance = zoom().scaleExtent([state.minZoom, state.maxZoom]).translateExtent(currentTranslateExtent);
+ const { minZoom, maxZoom, translateExtent } = store.getState();
+ const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
const selection = select(zoomPane.current as Element).call(d3ZoomInstance);
- const clampedX = clamp(defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0]);
- const clampedY = clamp(defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1]);
- const clampedZoom = clamp(defaultZoom, state.minZoom, state.maxZoom);
+ const clampedX = clamp(defaultPosition[0], translateExtent[0][0], translateExtent[1][0]);
+ const clampedY = clamp(defaultPosition[1], translateExtent[0][1], translateExtent[1][1]);
+ const clampedZoom = clamp(defaultZoom, minZoom, maxZoom);
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom);
d3ZoomInstance.transform(selection, updatedTransform);
- initD3Zoom({
+ store.setState({
d3Zoom: d3ZoomInstance,
d3Selection: selection,
d3ZoomHandler: selection.on('wheel.zoom'),
@@ -106,7 +105,7 @@ const ZoomPane = ({
if (panOnScroll && !zoomActivationKeyPressed) {
d3Selection
.on('wheel', (event: any) => {
- if (hasNoWheelClass(event)) {
+ if (isWrappedWithClass(event, noWheelClassName)) {
return false;
}
event.preventDefault();
@@ -140,7 +139,7 @@ const ZoomPane = ({
} else if (typeof d3ZoomHandler !== 'undefined') {
d3Selection
.on('wheel', (event: any) => {
- if (!preventScrolling || hasNoWheelClass(event)) {
+ if (!preventScrolling || isWrappedWithClass(event, noWheelClassName)) {
return null;
}
@@ -158,6 +157,7 @@ const ZoomPane = ({
zoomActivationKeyPressed,
zoomOnPinch,
preventScrolling,
+ noWheelClassName,
]);
useEffect(() => {
@@ -165,26 +165,26 @@ const ZoomPane = ({
if (selectionKeyPressed) {
d3Zoom.on('zoom', null);
} else {
- d3Zoom.on('zoom', (event: any) => {
- updateTransform([event.transform.x, event.transform.y, event.transform.k]);
+ d3Zoom.on('zoom', (event: D3ZoomEvent) => {
+ store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
if (onMove) {
const flowTransform = eventToFlowTransform(event.transform);
- onMove(flowTransform);
+ onMove(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
}
});
}
}
- }, [selectionKeyPressed, d3Zoom, updateTransform, onMove]);
+ }, [selectionKeyPressed, d3Zoom, onMove]);
useEffect(() => {
if (d3Zoom) {
if (onMoveStart) {
- d3Zoom.on('start', (event: any) => {
+ d3Zoom.on('start', (event: D3ZoomEvent) => {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.current = flowTransform;
- onMoveStart(flowTransform);
+ onMoveStart(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
});
} else {
d3Zoom.on('start', null);
@@ -195,12 +195,12 @@ const ZoomPane = ({
useEffect(() => {
if (d3Zoom) {
if (onMoveEnd) {
- d3Zoom.on('end', (event: any) => {
+ d3Zoom.on('end', (event: D3ZoomEvent) => {
if (viewChanged(prevTransform.current, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.current = flowTransform;
- onMoveEnd(flowTransform);
+ onMoveEnd(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
}
});
} else {
@@ -216,7 +216,7 @@ const ZoomPane = ({
const pinchZoom = zoomOnPinch && event.ctrlKey;
// if all interactions are disabled, we prevent all zoom events
- if (!paneMoveable && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
+ if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
return false;
}
@@ -230,20 +230,13 @@ const ZoomPane = ({
return false;
}
- if (hasNoWheelClass(event) && event.type === 'wheel') {
+ // if the target element is inside an element with the nowheel class, we prevent zooming
+ if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
return false;
}
- // when the target element is a node, we still allow zooming
- if (
- (event.target.closest('.react-flow__node') || event.target.closest('.react-flow__edge')) &&
- event.type !== 'wheel'
- ) {
- return false;
- }
-
- // when the target element is a node selection, we still allow zooming
- if (event.target.closest('.react-flow__nodesselection') && event.type !== 'wheel') {
+ // if the target element is inside an element with the nopan class, we prevent panning
+ if (isWrappedWithClass(event, noPanClassName) && event.type !== 'wheel') {
return false;
}
@@ -257,7 +250,7 @@ const ZoomPane = ({
}
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
- if (!paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) {
+ if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
return false;
}
@@ -271,14 +264,14 @@ const ZoomPane = ({
zoomOnPinch,
panOnScroll,
zoomOnDoubleClick,
- paneMoveable,
+ panOnDrag,
selectionKeyPressed,
elementsSelectable,
zoomActivationKeyPressed,
]);
return (
-
+
{children}
);
diff --git a/src/contexts/NodeIdContext.ts b/src/contexts/NodeIdContext.ts
index cc19227f..ba112d70 100644
--- a/src/contexts/NodeIdContext.ts
+++ b/src/contexts/NodeIdContext.ts
@@ -1,8 +1,6 @@
import { createContext } from 'react';
-import { ElementId } from '../types';
-
-type ContextProps = ElementId | null;
+type ContextProps = string | null;
export const NodeIdContext = createContext
>(null);
export const Provider = NodeIdContext.Provider;
diff --git a/src/custom.d.ts b/src/custom.d.ts
index 764a78c6..41c48654 100644
--- a/src/custom.d.ts
+++ b/src/custom.d.ts
@@ -14,3 +14,4 @@ declare module '*.svg' {
declare var __REACT_FLOW_VERSION__: string;
declare var __ENV__: string;
+declare var __INJECT_STYLES__: boolean;
diff --git a/src/hooks/useEdges.ts b/src/hooks/useEdges.ts
new file mode 100644
index 00000000..eb2fa464
--- /dev/null
+++ b/src/hooks/useEdges.ts
@@ -0,0 +1,12 @@
+import { useStore } from '../store';
+import { Edge, ReactFlowState } from '../types';
+
+const edgesSelector = (state: ReactFlowState) => state.edges;
+
+function useEdges(): Edge[] {
+ const edges = useStore(edgesSelector);
+
+ return edges;
+}
+
+export default useEdges;
diff --git a/src/hooks/useGlobalKeyHandler.ts b/src/hooks/useGlobalKeyHandler.ts
index c43997a9..1dff6db1 100644
--- a/src/hooks/useGlobalKeyHandler.ts
+++ b/src/hooks/useGlobalKeyHandler.ts
@@ -1,44 +1,78 @@
import { useEffect } from 'react';
+import shallow from 'zustand/shallow';
-import { useStore, useStoreActions } from '../store/hooks';
+import { useStore, useStoreApi } from '../store';
import useKeyPress from './useKeyPress';
-import { isNode, getConnectedEdges } from '../utils/graph';
-import { Elements, KeyCode, ElementId, FlowElement } from '../types';
+import { getConnectedEdges } from '../utils/graph';
+import { EdgeChange, KeyCode, NodeChange, ReactFlowState } from '../types';
interface HookParams {
- deleteKeyCode: KeyCode;
- multiSelectionKeyCode: KeyCode;
- onElementsRemove?: (elements: Elements) => void;
+ deleteKeyCode: KeyCode | null;
+ multiSelectionKeyCode: KeyCode | null;
}
-export default ({ deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: HookParams): void => {
- const store = useStore();
+const selector = (s: ReactFlowState) => ({
+ resetSelectedElements: s.resetSelectedElements,
+ onNodesChange: s.onNodesChange,
+ onEdgesChange: s.onEdgesChange,
+});
- const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
- const setMultiSelectionActive = useStoreActions((actions) => actions.setMultiSelectionActive);
- const resetSelectedElements = useStoreActions((actions) => actions.resetSelectedElements);
+export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
+ const store = useStoreApi();
+ const { resetSelectedElements, onNodesChange, onEdgesChange } = useStore(selector, shallow);
const deleteKeyPressed = useKeyPress(deleteKeyCode);
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
useEffect(() => {
- const { edges, selectedElements } = store.getState();
+ const { nodeInternals, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete } = store.getState();
+ // @TODO: work with nodeInternals instead of converting it to an array
+ const nodes = Array.from(nodeInternals).map(([_, node]) => node);
+ const selectedNodes = nodes.filter((n) => n.selected);
+ const selectedEdges = edges.filter((e) => e.selected);
- if (onElementsRemove && deleteKeyPressed && selectedElements) {
- const selectedNodes = selectedElements.filter(isNode);
+ if (deleteKeyPressed && (selectedNodes || selectedEdges)) {
const connectedEdges = getConnectedEdges(selectedNodes, edges);
- const elementsToRemove = [...selectedElements, ...connectedEdges].reduce(
- (res, item) => res.set(item.id, item),
- new Map()
- );
+ const edgesToRemove = [...selectedEdges, ...connectedEdges];
+ const edgeIdsToRemove = edgesToRemove.map((e) => e.id);
+
+ if (hasDefaultNodes) {
+ selectedNodes.forEach((node) => {
+ nodeInternals.delete(node.id);
+ });
+ }
+
+ if (hasDefaultEdges) {
+ store.setState({
+ nodeInternals: new Map(nodeInternals),
+ edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)),
+ });
+ }
+
+ onNodesDelete?.(selectedNodes);
+ onEdgesDelete?.(edgesToRemove);
+
+ if (onNodesChange) {
+ const nodeChanges: NodeChange[] = selectedNodes.map((n) => ({ id: n.id, type: 'remove' }));
+ onNodesChange(nodeChanges);
+ }
+
+ if (onEdgesChange) {
+ const edgeChanges: EdgeChange[] = edgeIdsToRemove.map((id) => ({
+ id,
+ type: 'remove',
+ }));
+
+ onEdgesChange(edgeChanges);
+ }
+
+ store.setState({ nodesSelectionActive: false });
- onElementsRemove(Array.from(elementsToRemove.values()));
- unsetNodesSelection();
resetSelectedElements();
}
- }, [deleteKeyPressed]);
+ }, [deleteKeyPressed, onNodesChange, onEdgesChange]);
useEffect(() => {
- setMultiSelectionActive(multiSelectionKeyPressed);
+ store.setState({ multiSelectionActive: multiSelectionKeyPressed });
}, [multiSelectionKeyPressed]);
};
diff --git a/src/hooks/useKeyPress.ts b/src/hooks/useKeyPress.ts
index 358a6d1a..54fad540 100644
--- a/src/hooks/useKeyPress.ts
+++ b/src/hooks/useKeyPress.ts
@@ -1,40 +1,113 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useRef, useMemo } from 'react';
-import { isInputDOMNode } from '../utils';
import { KeyCode } from '../types';
-export default (keyCode?: KeyCode): boolean => {
+type Keys = Array;
+type PressedKeys = Set;
+type KeyOrCode = 'key' | 'code';
+export interface UseKeyPressOptions {
+ target: Document | HTMLElement | ShadowRoot | null;
+}
+
+const doc = typeof document !== 'undefined' ? document : null;
+
+// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
+// a string means a single key 'a' or a combination when '+' is used 'a+d'
+// an array means different possibilites. Explainer: ['a', 'd+s'] here the
+// user can use the single key 'a' or the combination 'd' + 's'
+export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { target: doc }): boolean => {
const [keyPressed, setKeyPressed] = useState(false);
- useEffect(() => {
- if (typeof keyCode !== 'undefined') {
- const downHandler = (event: KeyboardEvent) => {
- if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
- event.preventDefault();
+ // we need to remember the pressed keys in order to support combinations
+ const pressedKeys = useRef(new Set([]));
+ // keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
+ // keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
+ // used to check if we store event.code or event.key. When the code is in the list of keysToWatch
+ // we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
+ // and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
+ // we can't find it in the list of keysToWatch.
+ const [keyCodes, keysToWatch] = useMemo<[Array, Keys]>(() => {
+ if (keyCode !== null) {
+ const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
+ const keys = keyCodeArr.filter((kc) => typeof kc === 'string').map((kc) => kc.split('+'));
+ const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
+
+ return [keys, keysFlat];
+ }
+
+ return [[], []];
+ }, [keyCode]);
+
+ useEffect(() => {
+ if (keyCode !== null) {
+ const downHandler = (event: KeyboardEvent) => {
+ const keyOrCode = useKeyOrCode(event.code, keysToWatch);
+ pressedKeys.current.add(event[keyOrCode]);
+
+ if (isMatchingKey(event, keyCodes, pressedKeys.current)) {
+ event.preventDefault();
setKeyPressed(true);
}
};
const upHandler = (event: KeyboardEvent) => {
- if (!isInputDOMNode(event) && (event.key === keyCode || event.keyCode === keyCode)) {
+ const keyOrCode = useKeyOrCode(event.code, keysToWatch);
+
+ if (isMatchingKey(event, keyCodes, pressedKeys.current)) {
setKeyPressed(false);
}
+
+ pressedKeys.current.delete(event[keyOrCode]);
};
- const resetHandler = () => setKeyPressed(false);
+ const resetHandler = () => {
+ pressedKeys.current.clear();
+ setKeyPressed(false);
+ };
- window.addEventListener('keydown', downHandler);
- window.addEventListener('keyup', upHandler);
- window.addEventListener('blur', resetHandler);
+ options?.target?.addEventListener('keydown', downHandler as EventListenerOrEventListenerObject);
+ options?.target?.addEventListener('keyup', upHandler as EventListenerOrEventListenerObject);
+ options?.target?.addEventListener('blur', resetHandler);
return () => {
- window.removeEventListener('keydown', downHandler);
- window.removeEventListener('keyup', upHandler);
- window.removeEventListener('blur', resetHandler);
+ pressedKeys.current.clear();
+
+ options?.target?.removeEventListener('keydown', downHandler as EventListenerOrEventListenerObject);
+ options?.target?.removeEventListener('keyup', upHandler as EventListenerOrEventListenerObject);
+ options?.target?.removeEventListener('blur', resetHandler);
};
}
}, [keyCode, setKeyPressed]);
return keyPressed;
};
+
+// utils
+
+function isMatchingKey(event: KeyboardEvent, keyCodes: Array, pressedKeys: PressedKeys): boolean {
+ if (isInputDOMNode(event)) {
+ return false;
+ }
+
+ return (
+ keyCodes
+ // we only want to compare same sizes of keyCode definitions
+ // and pressed keys. When the user specified 'Meta' as a key somewhere
+ // this would also be truthy without this filter when user presses 'Meta' + 'r'
+ .filter((keys) => keys.length === pressedKeys.size)
+ // since we want to support multiple possibilities only one of the
+ // combinations need to be part of the pressed keys
+ .some((keys) => keys.every((k) => pressedKeys.has(k)))
+ );
+}
+
+function useKeyOrCode(eventCode: string, keysToWatch: KeyCode): KeyOrCode {
+ return keysToWatch.includes(eventCode) ? 'code' : 'key';
+}
+
+function isInputDOMNode(e: KeyboardEvent): boolean {
+ const target = e?.target as HTMLElement;
+
+ return ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) || target?.hasAttribute('contenteditable');
+}
diff --git a/src/hooks/useNodes.ts b/src/hooks/useNodes.ts
new file mode 100644
index 00000000..8a728a2d
--- /dev/null
+++ b/src/hooks/useNodes.ts
@@ -0,0 +1,12 @@
+import { useStore } from '../store';
+import { Node, ReactFlowState } from '../types';
+
+const nodesSelector = (state: ReactFlowState) => Array.from(state.nodeInternals.values());
+
+function useNodes(): Node[] {
+ const nodes = useStore(nodesSelector);
+
+ return nodes;
+}
+
+export default useNodes;
diff --git a/src/hooks/useNodesEdgesState.ts b/src/hooks/useNodesEdgesState.ts
new file mode 100644
index 00000000..ad857568
--- /dev/null
+++ b/src/hooks/useNodesEdgesState.ts
@@ -0,0 +1,34 @@
+import { useState, useCallback, SetStateAction, Dispatch } from 'react';
+
+import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
+import { Node, NodeChange, Edge, EdgeChange } from '../types';
+
+type ApplyChanges = (changes: ChangesType[], items: ItemType[]) => ItemType[];
+type OnChange = (changes: ChangesType[]) => void;
+
+// returns a hook that can be used liked this:
+// const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
+function createUseItemsState(
+ applyChanges: ApplyChanges
+): (
+ initialItems: Node[]
+) => [Node[], Dispatch[]>>, OnChange];
+function createUseItemsState(
+ applyChanges: ApplyChanges
+): (
+ initialItems: Edge[]
+) => [Edge[], Dispatch[]>>, OnChange];
+function createUseItemsState(
+ applyChanges: ApplyChanges
+): (initialItems: any[]) => [any[], Dispatch>, OnChange] {
+ return (initialItems: any[]) => {
+ const [items, setItems] = useState(initialItems);
+
+ const onItemsChange = useCallback((changes: any[]) => setItems((items: any) => applyChanges(changes, items)), []);
+
+ return [items, setItems, onItemsChange];
+ };
+}
+
+export const useNodesState = createUseItemsState(applyNodeChanges);
+export const useEdgesState = createUseItemsState(applyEdgeChanges);
diff --git a/src/hooks/useOnInitHandler.ts b/src/hooks/useOnInitHandler.ts
new file mode 100644
index 00000000..28f4c8aa
--- /dev/null
+++ b/src/hooks/useOnInitHandler.ts
@@ -0,0 +1,18 @@
+import { useEffect, useRef } from 'react';
+
+import useReactFlow from './useReactFlow';
+import { OnInit } from '../types';
+
+function useOnInitHandler(onInit: OnInit | undefined) {
+ const ReactFlowInstance = useReactFlow();
+ const isInitialized = useRef(false);
+
+ useEffect(() => {
+ if (!isInitialized.current && ReactFlowInstance.viewportInitialized && onInit) {
+ setTimeout(() => onInit(ReactFlowInstance), 1);
+ isInitialized.current = true;
+ }
+ }, [onInit, ReactFlowInstance.viewportInitialized]);
+}
+
+export default useOnInitHandler;
diff --git a/src/hooks/useReactFlow.ts b/src/hooks/useReactFlow.ts
new file mode 100644
index 00000000..3be4d9a7
--- /dev/null
+++ b/src/hooks/useReactFlow.ts
@@ -0,0 +1,87 @@
+import { useCallback } from 'react';
+
+import useViewportHelper from './useViewportHelper';
+import { useStoreApi } from '../store';
+import { ReactFlowInstance, Instance } from '../types';
+
+export default function useReactFlow(): ReactFlowInstance {
+ const { initialized: viewportInitialized, ...viewportHelperFunctions } = useViewportHelper();
+ const store = useStoreApi();
+
+ const getNodes = useCallback>(() => {
+ const { nodeInternals } = store.getState();
+ const nodes = Array.from(nodeInternals.values());
+ return nodes.map((n) => ({ ...n }));
+ }, []);
+
+ const getNode = useCallback>((id) => {
+ const { nodeInternals } = store.getState();
+ return nodeInternals.get(id);
+ }, []);
+
+ const getEdges = useCallback>(() => {
+ const { edges = [] } = store.getState();
+ return edges.map((e) => ({ ...e }));
+ }, []);
+
+ const getEdge = useCallback>((id) => {
+ const { edges = [] } = store.getState();
+ return edges.find((e) => e.id === id);
+ }, []);
+
+ const setNodes = useCallback>((payload) => {
+ const { nodeInternals, setNodes } = store.getState();
+ const nodes = Array.from(nodeInternals.values());
+ const nextNodes = typeof payload === 'function' ? payload(nodes) : payload;
+ setNodes(nextNodes);
+ }, []);
+
+ const setEdges = useCallback>((payload) => {
+ const { edges = [], setEdges } = store.getState();
+ const nextEdges = typeof payload === 'function' ? payload(edges) : payload;
+ setEdges(nextEdges);
+ }, []);
+
+ const addNodes = useCallback>((payload) => {
+ const nodes = Array.isArray(payload) ? payload : [payload];
+ const { nodeInternals, setNodes } = store.getState();
+ const currentNodes = Array.from(nodeInternals.values());
+ const nextNodes = [...currentNodes, ...nodes];
+ setNodes(nextNodes);
+ }, []);
+
+ const addEdges = useCallback>((payload) => {
+ const nextEdges = Array.isArray(payload) ? payload : [payload];
+ const { edges = [], setEdges } = store.getState();
+ setEdges([...edges, ...nextEdges]);
+ }, []);
+
+ const toObject = useCallback>(() => {
+ const { nodeInternals, edges = [], transform } = store.getState();
+ const nodes = Array.from(nodeInternals.values());
+ const [x, y, zoom] = transform;
+ return {
+ nodes: nodes.map((n) => ({ ...n })),
+ edges: edges.map((e) => ({ ...e })),
+ viewport: {
+ x,
+ y,
+ zoom,
+ },
+ };
+ }, []);
+
+ return {
+ ...viewportHelperFunctions,
+ viewportInitialized,
+ getNodes,
+ getNode,
+ getEdges,
+ getEdge,
+ setNodes,
+ setEdges,
+ addNodes,
+ addEdges,
+ toObject,
+ };
+}
diff --git a/src/hooks/useResizeHandler.ts b/src/hooks/useResizeHandler.ts
index 164c7686..0c0573b4 100644
--- a/src/hooks/useResizeHandler.ts
+++ b/src/hooks/useResizeHandler.ts
@@ -1,10 +1,10 @@
import { useEffect, MutableRefObject } from 'react';
-import { useStoreActions } from '../store/hooks';
+import { useStoreApi } from '../store';
import { getDimensions } from '../utils';
-export default (rendererNode: MutableRefObject) => {
- const updateSize = useStoreActions((actions) => actions.updateSize);
+function useResizeHandler(rendererNode: MutableRefObject): void {
+ const store = useStoreApi();
useEffect(() => {
let resizeObserver: ResizeObserver;
@@ -20,7 +20,7 @@ export default (rendererNode: MutableRefObject) => {
console.warn('The React Flow parent container needs a width and a height to render the graph.');
}
- updateSize(size);
+ store.setState({ width: size.width || 500, height: size.height || 500 });
};
updateDimensions();
@@ -39,4 +39,6 @@ export default (rendererNode: MutableRefObject) => {
}
};
}, []);
-};
+}
+
+export default useResizeHandler;
diff --git a/src/hooks/useUpdateNodeInternals.ts b/src/hooks/useUpdateNodeInternals.ts
index 7d64af8a..7429f986 100644
--- a/src/hooks/useUpdateNodeInternals.ts
+++ b/src/hooks/useUpdateNodeInternals.ts
@@ -1,13 +1,15 @@
import { useCallback } from 'react';
-import { useStoreActions } from '../store/hooks';
-import { ElementId, UpdateNodeInternals } from '../types';
+import { useStore } from '../store';
+import { UpdateNodeInternals, ReactFlowState } from '../types';
+
+const selector = (state: ReactFlowState) => state.updateNodeDimensions;
function useUpdateNodeInternals(): UpdateNodeInternals {
- const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
+ const updateNodeDimensions = useStore(selector);
- return useCallback((id: ElementId) => {
- const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`);
+ return useCallback((id: string) => {
+ const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`) as HTMLDivElement;
if (nodeElement) {
updateNodeDimensions([{ id, nodeElement, forceUpdate: true }]);
diff --git a/src/hooks/useViewport.ts b/src/hooks/useViewport.ts
new file mode 100644
index 00000000..f4e61587
--- /dev/null
+++ b/src/hooks/useViewport.ts
@@ -0,0 +1,18 @@
+import shallow from 'zustand/shallow';
+
+import { useStore } from '../store';
+import { Viewport, ReactFlowState } from '../types';
+
+const viewportSelector = (state: ReactFlowState) => ({
+ x: state.transform[0],
+ y: state.transform[1],
+ zoom: state.transform[2],
+});
+
+function useViewport(): Viewport {
+ const viewport = useStore(viewportSelector, shallow);
+
+ return viewport;
+}
+
+export default useViewport;
diff --git a/src/hooks/useViewportHelper.ts b/src/hooks/useViewportHelper.ts
new file mode 100644
index 00000000..aeaabe61
--- /dev/null
+++ b/src/hooks/useViewportHelper.ts
@@ -0,0 +1,84 @@
+import { useMemo } from 'react';
+import { zoomIdentity } from 'd3-zoom';
+import shallow from 'zustand/shallow';
+
+import { useStoreApi, useStore } from '../store';
+import { pointToRendererPoint, getTransformForBounds, getD3Transition } from '../utils/graph';
+import { FitViewOptions, Viewport, ViewportHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types';
+import { fitView as fitViewStore } from '../store/utils';
+
+const DEFAULT_PADDING = 0.1;
+
+const initialViewportHelper: ViewportHelperFunctions = {
+ zoomIn: () => {},
+ zoomOut: () => {},
+ zoomTo: (_: number) => {},
+ getZoom: () => 1,
+ setViewport: (_: Viewport) => {},
+ getViewport: () => ({ x: 0, y: 0, zoom: 1 }),
+ fitView: (_: FitViewOptions = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {},
+ setCenter: (_: number, __: number) => {},
+ fitBounds: (_: Rect) => {},
+ project: (position: XYPosition) => position,
+ initialized: false,
+};
+
+const selector = (s: ReactFlowState) => ({
+ d3Zoom: s.d3Zoom,
+ d3Selection: s.d3Selection,
+});
+
+const useViewportHelper = (): ViewportHelperFunctions => {
+ const store = useStoreApi();
+ const { d3Zoom, d3Selection } = useStore(selector, shallow);
+
+ const viewportHelperFunctions = useMemo(() => {
+ if (d3Selection && d3Zoom) {
+ return {
+ zoomIn: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2),
+ zoomOut: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2),
+ zoomTo: (zoomLevel, options) => d3Zoom.scaleTo(getD3Transition(d3Selection, options?.duration), zoomLevel),
+ getZoom: () => {
+ const [, , zoom] = store.getState().transform;
+ return zoom;
+ },
+ setViewport: (transform, options) => {
+ const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
+ d3Zoom.transform(getD3Transition(d3Selection, options?.duration), nextTransform);
+ },
+ getViewport: () => {
+ const [x, y, zoom] = store.getState().transform;
+ return { x, y, zoom };
+ },
+ fitView: (options) => fitViewStore(store.getState, options),
+ setCenter: (x, y, options) => {
+ const { width, height, maxZoom } = store.getState();
+ const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
+ const centerX = width / 2 - x * nextZoom;
+ const centerY = height / 2 - y * nextZoom;
+ const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
+
+ d3Zoom.transform(getD3Transition(d3Selection, options?.duration), transform);
+ },
+ fitBounds: (bounds, options) => {
+ const { width, height, minZoom, maxZoom } = store.getState();
+ const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1);
+ const transform = zoomIdentity.translate(x, y).scale(zoom);
+
+ d3Zoom.transform(getD3Transition(d3Selection, options?.duration), transform);
+ },
+ project: (position: XYPosition) => {
+ const { transform, snapToGrid, snapGrid } = store.getState();
+ return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
+ },
+ initialized: true,
+ };
+ }
+
+ return initialViewportHelper;
+ }, [d3Zoom, d3Selection]);
+
+ return viewportHelperFunctions;
+};
+
+export default useViewportHelper;
diff --git a/src/hooks/useVisibleEdges.ts b/src/hooks/useVisibleEdges.ts
new file mode 100644
index 00000000..148683ba
--- /dev/null
+++ b/src/hooks/useVisibleEdges.ts
@@ -0,0 +1,83 @@
+import { useCallback } from 'react';
+
+import { useStore } from '../store';
+import { isEdgeVisible } from '../container/EdgeRenderer/utils';
+import { ReactFlowState, NodeInternals, Edge } from '../types';
+import { isNumeric } from '../utils';
+
+const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
+
+function groupEdgesByZLevel(edges: Edge[], nodeInternals: NodeInternals) {
+ let maxLevel = -1;
+
+ const levelLookup = edges.reduce>((tree, edge) => {
+ const z = isNumeric(edge.zIndex)
+ ? edge.zIndex
+ : Math.max(nodeInternals.get(edge.source)?.z || 0, nodeInternals.get(edge.target)?.z || 0);
+ if (tree[z]) {
+ tree[z].push(edge);
+ } else {
+ tree[z] = [edge];
+ }
+
+ maxLevel = z > maxLevel ? z : maxLevel;
+
+ return tree;
+ }, {});
+
+ const edgeTree = Object.entries(levelLookup).map(([key, edges]) => {
+ const level = +key;
+
+ return {
+ edges,
+ level,
+ isMaxLevel: level === maxLevel,
+ };
+ });
+
+ if (edgeTree.length === 0) {
+ return defaultEdgeTree;
+ }
+
+ return edgeTree;
+}
+
+function useVisibleEdges(onlyRenderVisible: boolean, nodeInternals: NodeInternals) {
+ const edges = useStore(
+ useCallback(
+ (s: ReactFlowState) => {
+ if (!onlyRenderVisible) {
+ return s.edges;
+ }
+
+ return s.edges.filter((e) => {
+ const sourceNode = nodeInternals.get(e.source);
+ const targetNode = nodeInternals.get(e.target);
+
+ return (
+ sourceNode?.width &&
+ sourceNode?.height &&
+ targetNode?.width &&
+ targetNode?.height &&
+ isEdgeVisible({
+ sourcePos: sourceNode.position || { x: 0, y: 0 },
+ targetPos: targetNode.position || { x: 0, y: 0 },
+ sourceWidth: sourceNode.width,
+ sourceHeight: sourceNode.height,
+ targetWidth: targetNode.width,
+ targetHeight: targetNode.height,
+ width: s.width,
+ height: s.height,
+ transform: s.transform,
+ })
+ );
+ });
+ },
+ [onlyRenderVisible, nodeInternals]
+ )
+ );
+
+ return groupEdgesByZLevel(edges, nodeInternals);
+}
+
+export default useVisibleEdges;
diff --git a/src/hooks/useVisibleNodes.ts b/src/hooks/useVisibleNodes.ts
new file mode 100644
index 00000000..8bd2e37d
--- /dev/null
+++ b/src/hooks/useVisibleNodes.ts
@@ -0,0 +1,22 @@
+import { useCallback } from 'react';
+
+import { useStore } from '../store';
+import { getNodesInside } from '../utils/graph';
+import { ReactFlowState } from '../types';
+
+function useVisibleNodes(onlyRenderVisible: boolean) {
+ const nodes = useStore(
+ useCallback(
+ (s: ReactFlowState) => {
+ return onlyRenderVisible
+ ? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
+ : Array.from(s.nodeInternals).map(([_, node]) => node);
+ },
+ [onlyRenderVisible]
+ )
+ );
+
+ return nodes;
+}
+
+export default useVisibleNodes;
diff --git a/src/hooks/useZoomPanHelper.ts b/src/hooks/useZoomPanHelper.ts
deleted file mode 100644
index d22b2f2d..00000000
--- a/src/hooks/useZoomPanHelper.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { useMemo } from 'react';
-import { zoomIdentity } from 'd3-zoom';
-import { Selection as D3Selection } from 'd3';
-
-import { useStoreState, useStore } from '../store/hooks';
-import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
-import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition } from '../types';
-
-const DEFAULT_PADDING = 0.1;
-
-const initialZoomPanHelper: ZoomPanHelperFunctions = {
- zoomIn: () => {},
- zoomOut: () => {},
- zoomTo: (_: number) => {},
- transform: (_: FlowTransform) => {},
- fitView: (_: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {},
- setCenter: (_: number, __: number) => {},
- fitBounds: (_: Rect) => {},
- project: (position: XYPosition) => position,
- initialized: false,
-};
-
-const getTransition = (selection: D3Selection, duration: number = 0) => {
- return selection.transition().duration(duration);
-};
-
-const useZoomPanHelper = (): ZoomPanHelperFunctions => {
- const store = useStore();
- const d3Zoom = useStoreState((s) => s.d3Zoom);
- const d3Selection = useStoreState((s) => s.d3Selection);
-
- const zoomPanHelperFunctions = useMemo(() => {
- if (d3Selection && d3Zoom) {
- return {
- zoomIn: (duration?: number) => d3Zoom.scaleBy(getTransition(d3Selection, duration), 1.2),
- zoomOut: (duration?: number) => d3Zoom.scaleBy(getTransition(d3Selection, duration), 1 / 1.2),
- zoomTo: (zoomLevel: number, duration?: number) =>
- d3Zoom.scaleTo(getTransition(d3Selection, duration), zoomLevel),
- transform: (transform: FlowTransform, duration?: number) => {
- const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
-
- d3Zoom.transform(getTransition(d3Selection, duration), nextTransform);
- },
- fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false, duration: 0 }) => {
- const { nodes, width, height, minZoom, maxZoom } = store.getState();
-
- if (!nodes.length) {
- return;
- }
-
- const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden));
- const [x, y, zoom] = getTransformForBounds(
- bounds,
- width,
- height,
- options.minZoom ?? minZoom,
- options.maxZoom ?? maxZoom,
- options.padding ?? DEFAULT_PADDING
- );
- const transform = zoomIdentity.translate(x, y).scale(zoom);
-
- d3Zoom.transform(getTransition(d3Selection, options.duration), transform);
- },
- setCenter: (x: number, y: number, zoom?: number, duration?: number) => {
- const { width, height, maxZoom } = store.getState();
-
- const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom;
- const centerX = width / 2 - x * nextZoom;
- const centerY = height / 2 - y * nextZoom;
- const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
- d3Zoom.transform(getTransition(d3Selection, duration), transform);
- },
- fitBounds: (bounds: Rect, padding = DEFAULT_PADDING, duration?: number) => {
- const { width, height, minZoom, maxZoom } = store.getState();
- const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
- const transform = zoomIdentity.translate(x, y).scale(zoom);
-
- d3Zoom.transform(getTransition(d3Selection, duration), transform);
- },
- project: (position: XYPosition) => {
- const { transform, snapToGrid, snapGrid } = store.getState();
- return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
- },
- initialized: true,
- };
- }
-
- return initialZoomPanHelper;
- }, [d3Zoom, d3Selection]);
-
- return zoomPanHelperFunctions;
-};
-
-export default useZoomPanHelper;
diff --git a/src/index.ts b/src/index.ts
index 589e49c8..826673c7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,13 +7,13 @@ export { default as EdgeText } from './components/Edges/EdgeText';
export { default as StraightEdge } from './components/Edges/StraightEdge';
export { default as StepEdge } from './components/Edges/StepEdge';
export { default as BezierEdge, getBezierPath } from './components/Edges/BezierEdge';
+export { default as SimpleBezierEdge, getSimpleBezierPath } from './components/Edges/SimpleBezierEdge';
export { default as SmoothStepEdge, getSmoothStepPath } from './components/Edges/SmoothStepEdge';
-export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils';
+export * from './additional-components';
export {
isNode,
isEdge,
- removeElements,
addEdge,
getOutgoers,
getIncomers,
@@ -22,14 +22,16 @@ export {
getTransformForBounds,
getRectOfNodes,
} from './utils/graph';
-export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
+export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
+export { getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils';
+
+export { default as useReactFlow } from './hooks/useReactFlow';
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
+export { default as useNodes } from './hooks/useNodes';
+export { default as useEdges } from './hooks/useEdges';
+export { default as useViewport } from './hooks/useViewport';
+export { default as useKeyPress } from './hooks/useKeyPress';
+export * from './hooks/useNodesEdgesState';
+export { useStore, useStoreApi } from './store';
-export * from './additional-components';
-export * from './store/hooks';
export * from './types';
-
-export { ReactFlowProps } from './container/ReactFlow';
-export { MiniMapProps } from './additional-components/MiniMap';
-export { ControlProps } from './additional-components/Controls';
-export { BackgroundProps } from './additional-components/Background';
diff --git a/src/store/actions.ts b/src/store/actions.ts
deleted file mode 100644
index 0f6bbc47..00000000
--- a/src/store/actions.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import { createAction } from './utils';
-
-import {
- Elements,
- OnConnectEndFunc,
- OnConnectFunc,
- OnConnectStartFunc,
- OnConnectStopFunc,
- NodeDimensionUpdate,
- NodePosUpdate,
- NodeDiffUpdate,
- XYPosition,
- Transform,
- Dimensions,
- InitD3ZoomPayload,
- TranslateExtent,
- SetConnectionId,
- SnapGrid,
- ConnectionMode,
- NodeExtent,
-} from '../types';
-
-import * as constants from './contants';
-
-export const setOnConnect = (onConnect: OnConnectFunc) =>
- createAction(constants.SET_ON_CONNECT, {
- onConnect,
- });
-
-export const setOnConnectStart = (onConnectStart: OnConnectStartFunc) =>
- createAction(constants.SET_ON_CONNECT_START, {
- onConnectStart,
- });
-
-export const setOnConnectStop = (onConnectStop: OnConnectStopFunc) =>
- createAction(constants.SET_ON_CONNECT_STOP, {
- onConnectStop,
- });
-
-export const setOnConnectEnd = (onConnectEnd: OnConnectEndFunc) =>
- createAction(constants.SET_ON_CONNECT_END, {
- onConnectEnd,
- });
-
-export const setElements = (elements: Elements) => createAction(constants.SET_ELEMENTS, elements);
-
-export const updateNodeDimensions = (updates: NodeDimensionUpdate[]) =>
- createAction(constants.UPDATE_NODE_DIMENSIONS, updates);
-
-export const updateNodePos = (payload: NodePosUpdate) => createAction(constants.UPDATE_NODE_POS, payload);
-
-export const updateNodePosDiff = (payload: NodeDiffUpdate) => createAction(constants.UPDATE_NODE_POS_DIFF, payload);
-
-export const setUserSelection = (mousePos: XYPosition) => createAction(constants.SET_USER_SELECTION, mousePos);
-
-export const updateUserSelection = (mousePos: XYPosition) => createAction(constants.UPDATE_USER_SELECTION, mousePos);
-
-export const unsetUserSelection = () => createAction(constants.UNSET_USER_SELECTION);
-
-export const setSelection = (selectionActive: boolean) =>
- createAction(constants.SET_SELECTION, {
- selectionActive,
- });
-
-export const unsetNodesSelection = () =>
- createAction(constants.UNSET_NODES_SELECTION, {
- nodesSelectionActive: false,
- });
-
-export const resetSelectedElements = () =>
- createAction(constants.RESET_SELECTED_ELEMENTS, {
- selectedElements: null,
- });
-
-export const setSelectedElements = (elements: Elements) => createAction(constants.SET_SELECTED_ELEMENTS, elements);
-
-export const addSelectedElements = (elements: Elements) => createAction(constants.ADD_SELECTED_ELEMENTS, elements);
-
-export const updateTransform = (transform: Transform) => createAction(constants.UPDATE_TRANSFORM, { transform });
-
-export const updateSize = (size: Dimensions) =>
- createAction(constants.UPDATE_SIZE, {
- width: size.width || 500,
- height: size.height || 500,
- });
-
-export const initD3Zoom = (payload: InitD3ZoomPayload) => createAction(constants.INIT_D3ZOOM, payload);
-
-export const setMinZoom = (minZoom: number) => createAction(constants.SET_MINZOOM, minZoom);
-
-export const setMaxZoom = (maxZoom: number) => createAction(constants.SET_MAXZOOM, maxZoom);
-
-export const setTranslateExtent = (translateExtent: TranslateExtent) =>
- createAction(constants.SET_TRANSLATEEXTENT, translateExtent);
-
-export const setConnectionPosition = (connectionPosition: XYPosition) =>
- createAction(constants.SET_CONNECTION_POSITION, { connectionPosition });
-
-export const setConnectionNodeId = (payload: SetConnectionId) => createAction(constants.SET_CONNECTION_NODEID, payload);
-
-export const setSnapToGrid = (snapToGrid: boolean) => createAction(constants.SET_SNAPTOGRID, { snapToGrid });
-
-export const setSnapGrid = (snapGrid: SnapGrid) => createAction(constants.SET_SNAPGRID, { snapGrid });
-
-export const setInteractive = (isInteractive: boolean) =>
- createAction(constants.SET_INTERACTIVE, {
- nodesDraggable: isInteractive,
- nodesConnectable: isInteractive,
- elementsSelectable: isInteractive,
- });
-
-export const setNodesDraggable = (nodesDraggable: boolean) =>
- createAction(constants.SET_NODES_DRAGGABLE, { nodesDraggable });
-
-export const setNodesConnectable = (nodesConnectable: boolean) =>
- createAction(constants.SET_NODES_CONNECTABLE, { nodesConnectable });
-
-export const setElementsSelectable = (elementsSelectable: boolean) =>
- createAction(constants.SET_ELEMENTS_SELECTABLE, { elementsSelectable });
-
-export const setMultiSelectionActive = (multiSelectionActive: boolean) =>
- createAction(constants.SET_MULTI_SELECTION_ACTIVE, { multiSelectionActive });
-
-export const setConnectionMode = (connectionMode: ConnectionMode) =>
- createAction(constants.SET_CONNECTION_MODE, { connectionMode });
-
-export const setNodeExtent = (nodeExtent: NodeExtent) => createAction(constants.SET_NODE_EXTENT, nodeExtent);
-
-export type ReactFlowAction = ReturnType<
- | typeof setOnConnect
- | typeof setOnConnectStart
- | typeof setOnConnectStop
- | typeof setOnConnectEnd
- | typeof setElements
- | typeof updateNodeDimensions
- | typeof updateNodePos
- | typeof updateNodePosDiff
- | typeof setUserSelection
- | typeof updateUserSelection
- | typeof unsetUserSelection
- | typeof setSelection
- | typeof unsetNodesSelection
- | typeof resetSelectedElements
- | typeof setSelectedElements
- | typeof addSelectedElements
- | typeof updateTransform
- | typeof updateSize
- | typeof initD3Zoom
- | typeof setMinZoom
- | typeof setMaxZoom
- | typeof setTranslateExtent
- | typeof setConnectionPosition
- | typeof setConnectionNodeId
- | typeof setSnapToGrid
- | typeof setSnapGrid
- | typeof setInteractive
- | typeof setNodesDraggable
- | typeof setNodesConnectable
- | typeof setElementsSelectable
- | typeof setMultiSelectionActive
- | typeof setConnectionMode
- | typeof setNodeExtent
->;
diff --git a/src/store/configure-store.ts b/src/store/configure-store.ts
deleted file mode 100644
index 6a33bc10..00000000
--- a/src/store/configure-store.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { createStore, Store } from 'redux';
-
-import { ReactFlowState } from '../types';
-import { ReactFlowAction } from './actions';
-import reactFlowReducer from './reducer';
-
-export default function configureStore(preloadedState: ReactFlowState): Store {
- const store = createStore(reactFlowReducer, preloadedState);
- return store;
-}
diff --git a/src/store/contants.ts b/src/store/contants.ts
deleted file mode 100644
index 389432df..00000000
--- a/src/store/contants.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-export const SET_ON_CONNECT = 'SET_ON_CONNECT';
-export const SET_ON_CONNECT_START = 'SET_ON_CONNECT_START';
-export const SET_ON_CONNECT_STOP = 'SET_ON_CONNECT_STOP';
-export const SET_ON_CONNECT_END = 'SET_ON_CONNECT_END';
-export const SET_ELEMENTS = 'SET_ELEMENTS';
-export const UPDATE_NODE_DIMENSIONS = 'UPDATE_NODE_DIMENSIONS';
-export const UPDATE_NODE_POS = 'UPDATE_NODE_POS';
-export const UPDATE_NODE_POS_DIFF = 'UPDATE_NODE_POS_DIFF';
-export const SET_USER_SELECTION = 'SET_USER_SELECTION';
-export const UPDATE_USER_SELECTION = 'UPDATE_USER_SELECTION';
-export const UNSET_USER_SELECTION = 'UNSET_USER_SELECTION';
-export const SET_SELECTION = 'SET_SELECTION';
-export const UNSET_NODES_SELECTION = 'UNSET_NODES_SELECTION';
-export const SET_SELECTED_ELEMENTS = 'SET_SELECTED_ELEMENTS';
-export const RESET_SELECTED_ELEMENTS = 'RESET_SELECTED_ELEMENTS';
-export const ADD_SELECTED_ELEMENTS = 'ADD_SELECTED_ELEMENTS';
-export const UPDATE_TRANSFORM = 'UPDATE_TRANSFORM';
-export const UPDATE_SIZE = 'UPDATE_SIZE';
-export const INIT_D3ZOOM = 'INIT_D3ZOOM';
-export const SET_MINZOOM = 'SET_MINZOOM';
-export const SET_MAXZOOM = 'SET_MAXZOOM';
-export const SET_TRANSLATEEXTENT = 'SET_TRANSLATEEXTENT';
-export const SET_CONNECTION_POSITION = 'SET_CONNECTION_POSITION';
-export const SET_CONNECTION_NODEID = 'SET_CONNECTION_NODEID';
-export const SET_SNAPTOGRID = 'SET_SNAPTOGRID';
-export const SET_SNAPGRID = 'SET_SNAPGRID';
-export const SET_INTERACTIVE = 'SET_INTERACTIVE';
-export const SET_NODES_DRAGGABLE = 'SET_NODES_DRAGGABLE';
-export const SET_NODES_CONNECTABLE = 'SET_NODES_CONNECTABLE';
-export const SET_ELEMENTS_SELECTABLE = 'SET_ELEMENTS_SELECTABLE';
-export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE';
-export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE';
-export const SET_NODE_EXTENT = 'SET_NODE_EXTENT';
diff --git a/src/store/hooks.ts b/src/store/hooks.ts
deleted file mode 100644
index 4ee7affc..00000000
--- a/src/store/hooks.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { bindActionCreators, Store, ActionCreator, ActionCreatorsMapObject } from 'redux';
-import {
- useStore as useStoreRedux,
- useSelector,
- useDispatch as reduxUseDispatch,
- TypedUseSelectorHook,
-} from 'react-redux';
-import { useMemo } from 'react';
-
-import { ReactFlowDispatch } from './index';
-import * as actions from './actions';
-import { ReactFlowAction } from './actions';
-import { ReactFlowState } from '../types';
-
-export const useTypedSelector: TypedUseSelectorHook = useSelector;
-
-export type ActionCreatorSelector = (acts: typeof actions) => ActionCreator;
-export type ActionMapObjectSelector = (acts: typeof actions) => ActionCreatorsMapObject;
-export type ActionSelector = (acts: typeof actions) => ActionCreatorsMapObject | ActionCreator;
-
-export function useStoreActions(
- actionSelector: ActionCreatorSelector
-): ActionCreator;
-
-export function useStoreActions(
- actionSelector: ActionMapObjectSelector
-): ActionCreatorsMapObject;
-
-export function useStoreActions(actionSelector: ActionSelector) {
- const dispatch: ReactFlowDispatch = reduxUseDispatch();
- const currAction = actionSelector(actions);
-
- const action = useMemo(() => {
- // this looks weird but required if both ActionSelector and ActionMapObjectSelector are supported
- return typeof currAction === 'function'
- ? bindActionCreators(currAction, dispatch)
- : bindActionCreators(currAction, dispatch);
- }, [dispatch, currAction]);
-
- return action;
-}
-
-export const useStoreState = useTypedSelector;
-export const useStore = (): Store => {
- const store = useStoreRedux();
- return store;
-};
-export const useDispatch: ReactFlowDispatch = reduxUseDispatch;
diff --git a/src/store/index.ts b/src/store/index.ts
index cf24e6b9..14caeeec 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1,64 +1,287 @@
-import { Store } from 'redux';
-import configureStore from './configure-store';
+import create from 'zustand';
+import createContext from 'zustand/context';
-import { ReactFlowState, ConnectionMode } from '../types';
+import { clampPosition, getDimensions } from '../utils';
+import { applyNodeChanges } from '../utils/changes';
-export const initialState: ReactFlowState = {
- width: 0,
- height: 0,
- transform: [0, 0, 1],
- nodes: [],
- edges: [],
- selectedElements: null,
- selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
+import {
+ ReactFlowState,
+ Node,
+ Edge,
+ NodeDimensionUpdate,
+ NodeDiffUpdate,
+ CoordinateExtent,
+ NodeDimensionChange,
+ EdgeSelectionChange,
+ NodeSelectionChange,
+ NodePositionChange,
+} from '../types';
+import { getHandleBounds } from '../components/Nodes/utils';
+import { createSelectionChange, getSelectionChanges } from '../utils/changes';
+import {
+ createNodeInternals,
+ createPositionChange,
+ handleControlledEdgeSelectionChange,
+ handleControlledNodeSelectionChange,
+ isParentSelected,
+ fitView,
+} from './utils';
+import initialState from './initialState';
- d3Zoom: null,
- d3Selection: null,
- d3ZoomHandler: undefined,
- minZoom: 0.5,
- maxZoom: 2,
- translateExtent: [
- [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
- [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
- ],
+const { Provider, useStore, useStoreApi } = createContext();
- nodeExtent: [
- [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
- [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
- ],
+const createStore = () =>
+ create((set, get) => ({
+ ...initialState,
+ setNodes: (nodes: Node[]) => {
+ set({ nodeInternals: createNodeInternals(nodes, get().nodeInternals) });
+ },
+ setEdges: (edges: Edge[]) => {
+ const { defaultEdgeOptions } = get();
- nodesSelectionActive: false,
- selectionActive: false,
+ if (defaultEdgeOptions) {
+ set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
+ } else {
+ set({ edges });
+ }
+ },
+ setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
+ const hasDefaultNodes = typeof nodes !== 'undefined';
+ const hasDefaultEdges = typeof edges !== 'undefined';
- userSelectionRect: {
- startX: 0,
- startY: 0,
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- draw: false,
- },
- connectionNodeId: null,
- connectionHandleId: null,
- connectionHandleType: 'source',
- connectionPosition: { x: 0, y: 0 },
- connectionMode: ConnectionMode.Strict,
+ const nodeInternals = hasDefaultNodes ? createNodeInternals(nodes, new Map()) : new Map();
+ const nextEdges = hasDefaultEdges ? edges : [];
- snapGrid: [15, 15],
- snapToGrid: false,
+ set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
+ },
+ updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
+ const { onNodesChange, transform, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get();
- nodesDraggable: true,
- nodesConnectable: true,
- elementsSelectable: true,
+ const changes: NodeDimensionChange[] = updates.reduce((res, update) => {
+ const node = nodeInternals.get(update.id);
- multiSelectionActive: false,
+ if (node) {
+ const dimensions = getDimensions(update.nodeElement);
+ const doUpdate = !!(
+ dimensions.width &&
+ dimensions.height &&
+ (node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate)
+ );
- reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
-};
+ if (doUpdate) {
+ const handleBounds = getHandleBounds(update.nodeElement, transform[2]);
+ nodeInternals.set(node.id, {
+ ...node,
+ handleBounds,
+ ...dimensions,
+ });
-const store: Store = configureStore(initialState);
+ res.push({
+ id: node.id,
+ type: 'dimensions',
+ dimensions,
+ });
+ }
+ }
-export type ReactFlowDispatch = typeof store.dispatch;
+ return res;
+ }, []);
-export default store;
+ const nextFitViewOnInitDone =
+ fitViewOnInitDone ||
+ (fitViewOnInit && !fitViewOnInitDone && fitView(get, { initial: true, ...fitViewOnInitOptions }));
+ set({ nodeInternals: new Map(nodeInternals), fitViewOnInitDone: nextFitViewOnInitDone });
+
+ if (changes?.length > 0) {
+ onNodesChange?.(changes);
+ }
+ },
+ updateNodePosition: ({ id, diff, dragging }: NodeDiffUpdate) => {
+ const { onNodesChange, nodeExtent, nodeInternals, hasDefaultNodes } = get();
+
+ if (hasDefaultNodes || onNodesChange) {
+ const changes: NodePositionChange[] = [];
+
+ nodeInternals.forEach((node) => {
+ if (node.selected) {
+ if (!node.parentNode || !isParentSelected(node, nodeInternals)) {
+ changes.push(createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals }));
+ }
+ } else if (node.id === id) {
+ changes.push(createPositionChange({ node, diff, dragging, nodeExtent, nodeInternals }));
+ }
+ });
+
+ if (changes?.length) {
+ if (hasDefaultNodes) {
+ const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values()));
+ const nextNodeInternals = createNodeInternals(nodes, nodeInternals);
+ set({ nodeInternals: nextNodeInternals });
+ }
+
+ onNodesChange?.(changes);
+ }
+ }
+ },
+ // @TODO: can we unify addSelectedNodes and addSelectedEdges somehow?
+ addSelectedNodes: (selectedNodeIds: string[]) => {
+ const {
+ multiSelectionActive,
+ onNodesChange,
+ nodeInternals,
+ hasDefaultNodes,
+ onEdgesChange,
+ hasDefaultEdges,
+ edges,
+ } = get();
+ let changedNodes: NodeSelectionChange[];
+ let changedEdges: EdgeSelectionChange[] | null = null;
+
+ if (multiSelectionActive) {
+ changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true)) as NodeSelectionChange[];
+ } else {
+ changedNodes = getSelectionChanges(Array.from(nodeInternals.values()), selectedNodeIds);
+ changedEdges = getSelectionChanges(edges, []);
+ }
+
+ if (changedNodes.length) {
+ if (hasDefaultNodes) {
+ set({ nodeInternals: handleControlledNodeSelectionChange(changedNodes, nodeInternals) });
+ }
+
+ onNodesChange?.(changedNodes);
+ }
+
+ if (changedEdges?.length) {
+ if (hasDefaultEdges) {
+ set({ edges: handleControlledEdgeSelectionChange(changedEdges, edges) });
+ }
+
+ onEdgesChange?.(changedEdges);
+ }
+ },
+ addSelectedEdges: (selectedEdgeIds: string[]) => {
+ const {
+ multiSelectionActive,
+ onEdgesChange,
+ edges,
+ hasDefaultEdges,
+ nodeInternals,
+ hasDefaultNodes,
+ onNodesChange,
+ } = get();
+ let changedEdges: EdgeSelectionChange[];
+ let changedNodes: NodeSelectionChange[] | null = null;
+
+ if (multiSelectionActive) {
+ changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true)) as EdgeSelectionChange[];
+ } else {
+ changedEdges = getSelectionChanges(edges, selectedEdgeIds);
+ changedNodes = getSelectionChanges(Array.from(nodeInternals.values()), []);
+ }
+
+ if (changedEdges.length) {
+ if (hasDefaultEdges) {
+ set({
+ edges: handleControlledEdgeSelectionChange(changedEdges, edges),
+ });
+ }
+ onEdgesChange?.(changedEdges);
+ }
+
+ if (changedNodes?.length) {
+ if (hasDefaultNodes) {
+ set({ nodeInternals: handleControlledNodeSelectionChange(changedNodes, nodeInternals) });
+ }
+
+ onNodesChange?.(changedNodes);
+ }
+ },
+ unselectNodesAndEdges: () => {
+ const { nodeInternals, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get();
+ const nodes = Array.from(nodeInternals.values());
+
+ const nodesToUnselect = nodes.map((n) => {
+ n.selected = false;
+ return createSelectionChange(n.id, false);
+ }) as NodeSelectionChange[];
+ const edgesToUnselect = edges.map((edge) => createSelectionChange(edge.id, false)) as EdgeSelectionChange[];
+
+ if (nodesToUnselect.length) {
+ if (hasDefaultNodes) {
+ set({ nodeInternals: handleControlledNodeSelectionChange(nodesToUnselect, nodeInternals) });
+ }
+ onNodesChange?.(nodesToUnselect);
+ }
+ if (edgesToUnselect.length) {
+ if (hasDefaultEdges) {
+ set({
+ edges: handleControlledEdgeSelectionChange(edgesToUnselect, edges),
+ });
+ }
+ onEdgesChange?.(edgesToUnselect);
+ }
+ },
+
+ setMinZoom: (minZoom: number) => {
+ const { d3Zoom, maxZoom } = get();
+ d3Zoom?.scaleExtent([minZoom, maxZoom]);
+
+ set({ minZoom });
+ },
+ setMaxZoom: (maxZoom: number) => {
+ const { d3Zoom, minZoom } = get();
+ d3Zoom?.scaleExtent([minZoom, maxZoom]);
+
+ set({ maxZoom });
+ },
+ setTranslateExtent: (translateExtent: CoordinateExtent) => {
+ const { d3Zoom } = get();
+ d3Zoom?.translateExtent(translateExtent);
+
+ set({ translateExtent });
+ },
+ resetSelectedElements: () => {
+ const { nodeInternals, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get();
+ const nodes = Array.from(nodeInternals.values());
+
+ const nodesToUnselect = nodes
+ .filter((e) => e.selected)
+ .map((n) => createSelectionChange(n.id, false)) as NodeSelectionChange[];
+ const edgesToUnselect = edges
+ .filter((e) => e.selected)
+ .map((e) => createSelectionChange(e.id, false)) as EdgeSelectionChange[];
+
+ if (nodesToUnselect.length) {
+ if (hasDefaultNodes) {
+ set({
+ nodeInternals: handleControlledNodeSelectionChange(nodesToUnselect, nodeInternals),
+ });
+ }
+ onNodesChange?.(nodesToUnselect);
+ }
+ if (edgesToUnselect.length) {
+ if (hasDefaultEdges) {
+ set({
+ edges: handleControlledEdgeSelectionChange(edgesToUnselect, edges),
+ });
+ }
+ onEdgesChange?.(edgesToUnselect);
+ }
+ },
+ setNodeExtent: (nodeExtent: CoordinateExtent) => {
+ const { nodeInternals } = get();
+
+ nodeInternals.forEach((node) => {
+ node.positionAbsolute = clampPosition(node.position, nodeExtent);
+ });
+
+ set({
+ nodeExtent,
+ nodeInternals: new Map(nodeInternals),
+ });
+ },
+ reset: () => set({ ...initialState }),
+ }));
+
+export { Provider, useStore, createStore, useStoreApi };
diff --git a/src/store/initialState.ts b/src/store/initialState.ts
new file mode 100644
index 00000000..e5280f43
--- /dev/null
+++ b/src/store/initialState.ts
@@ -0,0 +1,52 @@
+import { CoordinateExtent, ReactFlowStore, ConnectionMode } from '../types';
+
+const infiniteExtent: CoordinateExtent = [
+ [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
+ [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
+];
+
+const initialState: ReactFlowStore = {
+ width: 0,
+ height: 0,
+ transform: [0, 0, 1],
+ nodeInternals: new Map(),
+ edges: [],
+ onNodesChange: null,
+ onEdgesChange: null,
+ hasDefaultNodes: false,
+ hasDefaultEdges: false,
+ selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
+ d3Zoom: null,
+ d3Selection: null,
+ d3ZoomHandler: undefined,
+ minZoom: 0.5,
+ maxZoom: 2,
+ translateExtent: infiniteExtent,
+ nodeExtent: infiniteExtent,
+ nodesSelectionActive: false,
+ userSelectionActive: false,
+ connectionNodeId: null,
+ connectionHandleId: null,
+ connectionHandleType: 'source',
+ connectionPosition: { x: 0, y: 0 },
+ connectionMode: ConnectionMode.Strict,
+
+ snapGrid: [15, 15],
+ snapToGrid: false,
+
+ nodesDraggable: true,
+ nodesConnectable: true,
+ elementsSelectable: true,
+ fitViewOnInit: false,
+ fitViewOnInitDone: false,
+ fitViewOnInitOptions: undefined,
+
+ multiSelectionActive: false,
+
+ reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
+
+ connectionStartHandle: null,
+ connectOnClick: true,
+};
+
+export default initialState;
diff --git a/src/store/reducer.ts b/src/store/reducer.ts
deleted file mode 100644
index 617fda63..00000000
--- a/src/store/reducer.ts
+++ /dev/null
@@ -1,341 +0,0 @@
-import isEqual from 'fast-deep-equal';
-
-import { clampPosition, getDimensions } from '../utils';
-import {
- getNodesInside,
- getConnectedEdges,
- getRectOfNodes,
- isNode,
- isEdge,
- parseNode,
- parseEdge,
-} from '../utils/graph';
-import { getHandleBounds } from '../components/Nodes/utils';
-
-import { ReactFlowState, Node, XYPosition, Edge } from '../types';
-import * as constants from './contants';
-import { ReactFlowAction } from './actions';
-
-import { initialState } from './index';
-
-type NextElements = {
- nextNodes: Node[];
- nextEdges: Edge[];
-};
-
-export default function reactFlowReducer(state = initialState, action: ReactFlowAction): ReactFlowState {
- switch (action.type) {
- case constants.SET_ELEMENTS: {
- const propElements = action.payload;
- const nextElements: NextElements = {
- nextNodes: [],
- nextEdges: [],
- };
- const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => {
- if (isNode(propElement)) {
- const storeNode = state.nodes.find((node) => node.id === propElement.id);
-
- if (storeNode) {
- const updatedNode: Node = {
- ...storeNode,
- ...propElement,
- };
-
- if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) {
- updatedNode.__rf.position = propElement.position;
- }
-
- if (typeof propElement.type !== 'undefined' && propElement.type !== storeNode.type) {
- // we reset the elements dimensions here in order to force a re-calculation of the bounds.
- // When the type of a node changes it is possible that the number or positions of handles changes too.
- updatedNode.__rf.width = null;
- }
-
- res.nextNodes.push(updatedNode);
- } else {
- res.nextNodes.push(parseNode(propElement, state.nodeExtent));
- }
- } else if (isEdge(propElement)) {
- const storeEdge = state.edges.find((se) => se.id === propElement.id);
-
- if (storeEdge) {
- res.nextEdges.push({
- ...storeEdge,
- ...propElement,
- });
- } else {
- res.nextEdges.push(parseEdge(propElement));
- }
- }
-
- return res;
- }, nextElements);
-
- return { ...state, nodes: nextNodes, edges: nextEdges };
- }
- case constants.UPDATE_NODE_DIMENSIONS: {
- const updatedNodes = state.nodes.map((node) => {
- const update = action.payload.find((u) => u.id === node.id);
- if (update) {
- const dimensions = getDimensions(update.nodeElement);
- const doUpdate =
- dimensions.width &&
- dimensions.height &&
- (node.__rf.width !== dimensions.width || node.__rf.height !== dimensions.height || update.forceUpdate);
-
- if (doUpdate) {
- const handleBounds = getHandleBounds(update.nodeElement, state.transform[2]);
-
- return {
- ...node,
- __rf: {
- ...node.__rf,
- ...dimensions,
- handleBounds,
- },
- };
- }
- }
-
- return node;
- });
-
- return {
- ...state,
- nodes: updatedNodes,
- };
- }
- case constants.UPDATE_NODE_POS: {
- const { id, pos } = action.payload;
- let position: XYPosition = pos;
-
- if (state.snapToGrid) {
- const [gridSizeX, gridSizeY] = state.snapGrid;
- position = {
- x: gridSizeX * Math.round(pos.x / gridSizeX),
- y: gridSizeY * Math.round(pos.y / gridSizeY),
- };
- }
-
- const nextNodes = state.nodes.map((node) => {
- if (node.id === id) {
- return {
- ...node,
- __rf: {
- ...node.__rf,
- position,
- },
- };
- }
-
- return node;
- });
-
- return { ...state, nodes: nextNodes };
- }
- case constants.UPDATE_NODE_POS_DIFF: {
- const { id, diff, isDragging } = action.payload;
-
- const nextNodes = state.nodes.map((node) => {
- if (id === node.id || state.selectedElements?.find((sNode) => sNode.id === node.id)) {
- const updatedNode = {
- ...node,
- __rf: {
- ...node.__rf,
- isDragging,
- },
- };
-
- if (diff) {
- updatedNode.__rf.position = {
- x: node.__rf.position.x + diff.x,
- y: node.__rf.position.y + diff.y,
- };
- }
-
- return updatedNode;
- }
-
- return node;
- });
-
- return { ...state, nodes: nextNodes };
- }
- case constants.SET_USER_SELECTION: {
- const mousePos = action.payload;
-
- return {
- ...state,
- selectionActive: true,
- userSelectionRect: {
- width: 0,
- height: 0,
- startX: mousePos.x,
- startY: mousePos.y,
- x: mousePos.x,
- y: mousePos.y,
- draw: true,
- },
- };
- }
- case constants.UPDATE_USER_SELECTION: {
- const mousePos = action.payload;
- const startX = state.userSelectionRect.startX ?? 0;
- const startY = state.userSelectionRect.startY ?? 0;
-
- const nextUserSelectRect = {
- ...state.userSelectionRect,
- x: mousePos.x < startX ? mousePos.x : startX,
- y: mousePos.y < startY ? mousePos.y : startY,
- width: Math.abs(mousePos.x - startX),
- height: Math.abs(mousePos.y - startY),
- };
-
- const selectedNodes = getNodesInside(state.nodes, nextUserSelectRect, state.transform, false, true);
- const selectedEdges = getConnectedEdges(selectedNodes, state.edges);
-
- const nextSelectedElements = [...selectedNodes, ...selectedEdges];
- const selectedElementsChanged = !isEqual(nextSelectedElements, state.selectedElements);
- const selectedElementsUpdate = selectedElementsChanged
- ? {
- selectedElements: nextSelectedElements.length > 0 ? nextSelectedElements : null,
- }
- : {};
-
- return {
- ...state,
- ...selectedElementsUpdate,
- userSelectionRect: nextUserSelectRect,
- };
- }
- case constants.UNSET_USER_SELECTION: {
- const selectedNodes = state.selectedElements?.filter((node) => isNode(node) && node.__rf) as Node[];
-
- const stateUpdate = {
- ...state,
- selectionActive: false,
- userSelectionRect: {
- ...state.userSelectionRect,
- draw: false,
- },
- };
-
- if (!selectedNodes || selectedNodes.length === 0) {
- stateUpdate.selectedElements = null;
- stateUpdate.nodesSelectionActive = false;
- } else {
- const selectedNodesBbox = getRectOfNodes(selectedNodes);
- stateUpdate.selectedNodesBbox = selectedNodesBbox;
- stateUpdate.nodesSelectionActive = true;
- }
-
- return stateUpdate;
- }
- case constants.SET_SELECTED_ELEMENTS: {
- const elements = action.payload;
- const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
- const selectedElementsUpdated = !isEqual(selectedElementsArr, state.selectedElements);
- const selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements;
-
- return {
- ...state,
- selectedElements,
- };
- }
- case constants.ADD_SELECTED_ELEMENTS: {
- const { multiSelectionActive, selectedElements } = state;
- const elements = action.payload;
- const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
-
- let nextElements = selectedElementsArr;
-
- if (multiSelectionActive) {
- nextElements = selectedElements ? [...selectedElements, ...selectedElementsArr] : selectedElementsArr;
- }
-
- const selectedElementsUpdated = !isEqual(nextElements, state.selectedElements);
- const nextSelectedElements = selectedElementsUpdated ? nextElements : state.selectedElements;
-
- return { ...state, selectedElements: nextSelectedElements };
- }
- case constants.INIT_D3ZOOM: {
- const { d3Zoom, d3Selection, d3ZoomHandler, transform } = action.payload;
-
- return {
- ...state,
- d3Zoom,
- d3Selection,
- d3ZoomHandler,
- transform,
- };
- }
- case constants.SET_MINZOOM: {
- const minZoom = action.payload;
-
- state.d3Zoom?.scaleExtent([minZoom, state.maxZoom]);
-
- return {
- ...state,
- minZoom,
- };
- }
-
- case constants.SET_MAXZOOM: {
- const maxZoom = action.payload;
-
- state.d3Zoom?.scaleExtent([state.minZoom, maxZoom]);
-
- return {
- ...state,
- maxZoom,
- };
- }
- case constants.SET_TRANSLATEEXTENT: {
- const translateExtent = action.payload;
-
- state.d3Zoom?.translateExtent(translateExtent);
-
- return {
- ...state,
- translateExtent,
- };
- }
- case constants.SET_NODE_EXTENT: {
- const nodeExtent = action.payload;
- return {
- ...state,
- nodeExtent,
- nodes: state.nodes.map((node) => {
- return {
- ...node,
- __rf: {
- ...node.__rf,
- position: clampPosition(node.__rf.position, nodeExtent),
- },
- };
- }),
- };
- }
- case constants.SET_ON_CONNECT:
- case constants.SET_ON_CONNECT_START:
- case constants.SET_ON_CONNECT_STOP:
- case constants.SET_ON_CONNECT_END:
- case constants.RESET_SELECTED_ELEMENTS:
- case constants.UNSET_NODES_SELECTION:
- case constants.UPDATE_TRANSFORM:
- case constants.UPDATE_SIZE:
- case constants.SET_CONNECTION_POSITION:
- case constants.SET_CONNECTION_NODEID:
- case constants.SET_SNAPTOGRID:
- case constants.SET_SNAPGRID:
- case constants.SET_INTERACTIVE:
- case constants.SET_NODES_DRAGGABLE:
- case constants.SET_NODES_CONNECTABLE:
- case constants.SET_ELEMENTS_SELECTABLE:
- case constants.SET_MULTI_SELECTION_ACTIVE:
- case constants.SET_CONNECTION_MODE:
- return { ...state, ...action.payload };
- default:
- return state;
- }
-}
diff --git a/src/store/utils.ts b/src/store/utils.ts
index c4bfbaf3..a41799f8 100644
--- a/src/store/utils.ts
+++ b/src/store/utils.ts
@@ -1,5 +1,213 @@
-export function createAction(type: T): { type: T };
-export function createAction(type: T, payload: P): { type: T; payload: P };
-export function createAction(type: string, payload?: any) {
- return { type, payload };
+import { zoomIdentity } from 'd3-zoom';
+import { GetState } from 'zustand';
+
+import { clampPosition, isNumeric } from '../utils';
+import { getD3Transition, getRectOfNodes, getTransformForBounds } from '../utils/graph';
+import {
+ CoordinateExtent,
+ Edge,
+ EdgeSelectionChange,
+ Node,
+ NodeInternals,
+ NodePositionChange,
+ NodeSelectionChange,
+ ReactFlowState,
+ XYPosition,
+ XYZPosition,
+ FitViewOptions,
+} from '../types';
+
+type ParentNodes = Record;
+
+function calculateXYZPosition(
+ node: Node,
+ nodeInternals: NodeInternals,
+ parentNodes: ParentNodes,
+ result: XYZPosition
+): XYZPosition {
+ if (!node.parentNode) {
+ return result;
+ }
+ const parentNode = nodeInternals.get(node.parentNode)!;
+
+ return calculateXYZPosition(parentNode, nodeInternals, parentNodes, {
+ x: (result.x ?? 0) + (parentNode.position?.x ?? 0),
+ y: (result.y ?? 0) + (parentNode.position?.y ?? 0),
+ z: (parentNode.z ?? 0) > (node.z ?? 0) ? parentNode.z ?? 0 : node.z ?? 0,
+ });
+}
+
+export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals): NodeInternals {
+ const nextNodeInternals = new Map();
+ const parentNodes: ParentNodes = {};
+
+ nodes.forEach((node) => {
+ const z = isNumeric(node.zIndex) ? node.zIndex : node.dragging || node.selected ? 1000 : 0;
+ const internals: Node = {
+ ...nodeInternals.get(node.id),
+ ...node,
+ positionAbsolute: {
+ x: node.position.x,
+ y: node.position.y,
+ },
+ z,
+ };
+ if (node.parentNode) {
+ internals.parentNode = node.parentNode;
+ parentNodes[node.parentNode] = true;
+ }
+ nextNodeInternals.set(node.id, internals);
+ });
+
+ nextNodeInternals.forEach((node) => {
+ if (node.parentNode && !nextNodeInternals.has(node.parentNode)) {
+ throw new Error(`Parent node ${node.parentNode} not found`);
+ }
+
+ if (node.parentNode || parentNodes[node.id]) {
+ const { x, y, z } = calculateXYZPosition(node, nextNodeInternals, parentNodes, {
+ ...node.position,
+ z: node.z ?? 0,
+ });
+
+ node.positionAbsolute = {
+ x,
+ y,
+ };
+
+ node.z = z;
+
+ if (parentNodes[node.id]) {
+ node.isParent = true;
+ }
+ }
+ });
+
+ return nextNodeInternals;
+}
+
+export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean {
+ if (!node.parentNode) {
+ return false;
+ }
+
+ const parentNode = nodeInternals.get(node.parentNode);
+
+ if (!parentNode) {
+ return false;
+ }
+
+ if (parentNode.selected) {
+ return true;
+ }
+
+ return isParentSelected(parentNode, nodeInternals);
+}
+
+type CreatePostionChangeParams = {
+ node: Node;
+ nodeExtent: CoordinateExtent;
+ nodeInternals: NodeInternals;
+ diff?: XYPosition;
+ dragging?: boolean;
+};
+
+export function createPositionChange({
+ node,
+ diff,
+ dragging,
+ nodeExtent,
+ nodeInternals,
+}: CreatePostionChangeParams): NodePositionChange {
+ const change: NodePositionChange = {
+ id: node.id,
+ type: 'position',
+ dragging: !!dragging,
+ };
+
+ if (diff) {
+ const nextPosition = { x: node.position.x + diff.x, y: node.position.y + diff.y };
+ let currentExtent = nodeExtent || node.extent;
+
+ if (node.extent === 'parent' && node.parentNode && node.width && node.height) {
+ const parent = nodeInternals.get(node.parentNode);
+ currentExtent =
+ parent?.width && parent?.height
+ ? [
+ [0, 0],
+ [parent.width - node.width, parent.height - node.height],
+ ]
+ : currentExtent;
+ }
+
+ change.position = currentExtent ? clampPosition(nextPosition, currentExtent) : nextPosition;
+ }
+
+ return change;
+}
+
+type InternalFitViewOptions = {
+ initial?: boolean;
+} & FitViewOptions;
+
+export function fitView(get: GetState, options: InternalFitViewOptions = {}) {
+ let { nodeInternals, width, height, minZoom, maxZoom, d3Zoom, d3Selection, fitViewOnInitDone, fitViewOnInit } = get();
+
+ if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) {
+ if (d3Zoom && d3Selection) {
+ const nodes = Array.from(nodeInternals.values()).filter((n) =>
+ options.includeHiddenNodes ? !n.parentNode && n.width && n.height : !n.parentNode && !n.hidden
+ );
+
+ const nodesInitialized = nodes.every((n) => n.width && n.height);
+
+ if (nodes.length > 0 && nodesInitialized) {
+ const bounds = getRectOfNodes(nodes);
+ const [x, y, zoom] = getTransformForBounds(
+ bounds,
+ width,
+ height,
+ options.minZoom ?? minZoom,
+ options.maxZoom ?? maxZoom,
+ options.padding ?? 0.1
+ );
+
+ const nextTransform = zoomIdentity.translate(x, y).scale(zoom);
+
+ if (typeof options.duration === 'number' && options.duration > 0) {
+ d3Zoom.transform(getD3Transition(d3Selection, options.duration), nextTransform);
+ } else {
+ d3Zoom.transform(d3Selection, nextTransform);
+ }
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+export function handleControlledNodeSelectionChange(nodeChanges: NodeSelectionChange[], nodeInternals: NodeInternals) {
+ nodeChanges.forEach((change) => {
+ const node = nodeInternals.get(change.id);
+ if (node) {
+ nodeInternals.set(node.id, {
+ ...node,
+ selected: change.selected,
+ });
+ }
+ });
+
+ return new Map(nodeInternals);
+}
+
+export function handleControlledEdgeSelectionChange(edgeChanges: EdgeSelectionChange[], edges: Edge[]) {
+ return edges.map((e) => {
+ const change = edgeChanges.find((change) => change.id === e.id);
+ if (change) {
+ e.selected = change.selected;
+ }
+ return e;
+ });
}
diff --git a/src/style.css b/src/style.css
index 327cfbef..a5f8ef98 100644
--- a/src/style.css
+++ b/src/style.css
@@ -5,12 +5,10 @@
overflow: hidden;
}
-.react-flow__renderer,
-.react-flow__pane,
-.react-flow__selectionpane {
+.react-flow__container {
+ position: absolute;
width: 100%;
height: 100%;
- position: absolute;
top: 0;
left: 0;
}
@@ -19,6 +17,12 @@
z-index: 1;
}
+.react-flow__viewport {
+ transform-origin: 0 0;
+ z-index: 2;
+ pointer-events: none;
+}
+
.react-flow__renderer {
z-index: 4;
}
@@ -27,18 +31,9 @@
z-index: 5;
}
-.react-flow__selection {
- position: absolute;
- top: 0;
- left: 0;
-}
-
.react-flow__edges {
- position: absolute;
- top: 0;
- left: 0;
pointer-events: none;
- z-index: 2;
+ overflow: visible;
}
.react-flow__edge {
@@ -82,12 +77,8 @@
}
.react-flow__nodes {
- position: absolute;
- width: 100%;
- height: 100%;
pointer-events: none;
transform-origin: 0 0;
- z-index: 3;
}
.react-flow__node {
@@ -95,15 +86,11 @@
user-select: none;
pointer-events: all;
transform-origin: 0 0;
+ box-sizing: border-box;
}
.react-flow__nodesselection {
z-index: 3;
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
transform-origin: left top;
pointer-events: none;
@@ -154,19 +141,11 @@
/* additional components */
-.react-flow__background {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
-}
-
.react-flow__controls {
position: absolute;
z-index: 5;
- bottom: 10px;
- left: 10px;
+ bottom: 20px;
+ left: 15px;
&-button {
width: 24px;
@@ -182,6 +161,41 @@
.react-flow__minimap {
position: absolute;
z-index: 5;
- bottom: 10px;
- right: 10px;
+ bottom: 20px;
+ right: 15px;
+}
+
+.react-flow__attribution {
+ font-size: 10px;
+ position: absolute;
+ z-index: 1000;
+ background: rgba(255, 255, 255, 0.5);
+ padding: 2px 3px;
+ color: #999;
+
+ a {
+ color: #555;
+ text-decoration: none;
+ }
+
+ &.top {
+ top: 0;
+ }
+
+ &.bottom {
+ bottom: 0;
+ }
+
+ &.left {
+ left: 0;
+ }
+
+ &.right {
+ right: 0;
+ }
+
+ &.center {
+ left: 50%;
+ transform: translateX(-50%);
+ }
}
diff --git a/src/theme-default.css b/src/theme-default.css
index 73384c77..b5bc257a 100644
--- a/src/theme-default.css
+++ b/src/theme-default.css
@@ -1,8 +1,3 @@
-.react-flow__selection {
- background: rgba(0, 89, 220, 0.08);
- border: 1px dotted rgba(0, 89, 220, 0.8);
-}
-
.react-flow__edge {
&.selected {
.react-flow__edge-path {
@@ -46,7 +41,8 @@
.react-flow__node-default,
.react-flow__node-input,
-.react-flow__node-output {
+.react-flow__node-output,
+.react-flow__node-group {
padding: 10px;
border-radius: 3px;
width: 150px;
@@ -55,31 +51,6 @@
text-align: center;
border-width: 1px;
border-style: solid;
-}
-
-.react-flow__node-default.selectable,
-.react-flow__node-input.selectable,
-.react-flow__node-output.selectable {
- &:hover {
- box-shadow: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
- }
-}
-
-.react-flow__node-input {
- background: #fff;
- border-color: #0041d0;
-
- &.selected,
- &.selected:hover {
- box-shadow: 0 0 0 0.5px #0041d0;
- }
-
- .react-flow__handle {
- background: #0041d0;
- }
-}
-
-.react-flow__node-default {
background: #fff;
border-color: #1a192b;
@@ -93,21 +64,27 @@
}
}
-.react-flow__node-output {
- background: #fff;
- border-color: #ff0072;
-
- &.selected,
- &.selected:hover {
- box-shadow: 0 0 0 0.5px #ff0072;
- }
-
- .react-flow__handle {
- background: #ff0072;
+.react-flow__node-default.selectable,
+.react-flow__node-input.selectable,
+.react-flow__node-output.selectable,
+.react-flow__node-group.selectable {
+ &:hover {
+ box-shadow: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
}
}
-.react-flow__nodesselection-rect {
+.react-flow__node-group {
+ background: rgba(240, 240, 240, 0.25);
+ border-color: #1a192b;
+
+ &.selected,
+ &.selected:hover {
+ box-shadow: 0 0 0 0.5px #1a192b;
+ }
+}
+
+.react-flow__nodesselection-rect,
+.react-flow__selection {
background: rgba(0, 89, 220, 0.08);
border: 1px dotted rgba(0, 89, 220, 0.8);
}
diff --git a/src/types/changes.ts b/src/types/changes.ts
new file mode 100644
index 00000000..a071f463
--- /dev/null
+++ b/src/types/changes.ts
@@ -0,0 +1,33 @@
+import { XYPosition, Dimensions } from './utils';
+import { NodeHandleBounds } from './nodes';
+
+export type NodeDimensionChange = {
+ id: string;
+ type: 'dimensions';
+ dimensions: Dimensions;
+ handleBounds?: NodeHandleBounds;
+};
+
+export type NodePositionChange = {
+ id: string;
+ type: 'position';
+ position?: XYPosition;
+ dragging?: boolean;
+};
+
+export type NodeSelectionChange = {
+ id: string;
+ type: 'select';
+ selected: boolean;
+};
+
+export type NodeRemoveChange = {
+ id: string;
+ type: 'remove';
+};
+
+export type NodeChange = NodeDimensionChange | NodePositionChange | NodeSelectionChange | NodeRemoveChange;
+
+export type EdgeSelectionChange = NodeSelectionChange;
+export type EdgeRemoveChange = NodeRemoveChange;
+export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange;
diff --git a/src/types/component-props.ts b/src/types/component-props.ts
new file mode 100644
index 00000000..fa915184
--- /dev/null
+++ b/src/types/component-props.ts
@@ -0,0 +1,153 @@
+import React, { CSSProperties, HTMLAttributes, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
+
+import {
+ OnSelectionChangeFunc,
+ NodeTypes,
+ EdgeTypes,
+ Node,
+ Edge,
+ ConnectionMode,
+ ConnectionLineType,
+ ConnectionLineComponent,
+ OnConnectStart,
+ OnConnectStop,
+ OnConnectEnd,
+ OnConnect,
+ CoordinateExtent,
+ KeyCode,
+ PanOnScrollMode,
+ OnEdgeUpdateFunc,
+ OnInit,
+ ProOptions,
+ AttributionPosition,
+ DefaultEdgeOptions,
+ FitViewOptions,
+ BackgroundVariant,
+ OnNodesDelete,
+ OnEdgesDelete,
+ OnNodesChange,
+ OnEdgesChange,
+ OnMove,
+ OnMoveStart,
+ OnMoveEnd,
+} from '.';
+
+export interface ReactFlowProps extends HTMLAttributes {
+ nodes?: Node[];
+ edges?: Edge[];
+ defaultNodes?: Node[];
+ defaultEdges?: Edge[];
+ defaultEdgeOptions?: DefaultEdgeOptions;
+ onNodesChange?: OnNodesChange;
+ onEdgesChange?: OnEdgesChange;
+ onNodeClick?: (event: React.MouseEvent, node: Node) => void;
+ onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
+ onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeMouseLeave?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeContextMenu?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
+ onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
+ onNodesDelete?: OnNodesDelete;
+ onEdgesDelete?: OnEdgesDelete;
+ onConnect?: OnConnect;
+ onConnectStart?: OnConnectStart;
+ onConnectStop?: OnConnectStop;
+ onConnectEnd?: OnConnectEnd;
+ onInit?: OnInit;
+ onMove?: OnMove;
+ onMoveStart?: OnMoveStart;
+ onMoveEnd?: OnMoveEnd;
+ onSelectionChange?: OnSelectionChangeFunc;
+ onSelectionDragStart?: (event: ReactMouseEvent, nodes: Node[]) => void;
+ onSelectionDrag?: (event: ReactMouseEvent, nodes: Node[]) => void;
+ onSelectionDragStop?: (event: ReactMouseEvent, nodes: Node[]) => void;
+ onSelectionContextMenu?: (event: ReactMouseEvent, nodes: Node[]) => void;
+ onPaneScroll?: (event?: WheelEvent) => void;
+ onPaneClick?: (event: ReactMouseEvent) => void;
+ onPaneContextMenu?: (event: ReactMouseEvent) => void;
+ nodeTypes?: NodeTypes;
+ edgeTypes?: EdgeTypes;
+ connectionMode?: ConnectionMode;
+ connectionLineType?: ConnectionLineType;
+ connectionLineStyle?: CSSProperties;
+ connectionLineComponent?: ConnectionLineComponent;
+ deleteKeyCode?: KeyCode | null;
+ selectionKeyCode?: KeyCode | null;
+ multiSelectionKeyCode?: KeyCode | null;
+ zoomActivationKeyCode?: KeyCode;
+ snapToGrid?: boolean;
+ snapGrid?: [number, number];
+ onlyRenderVisibleElements?: boolean;
+ nodesDraggable?: boolean;
+ nodesConnectable?: boolean;
+ elementsSelectable?: boolean;
+ selectNodesOnDrag?: boolean;
+ panOnDrag?: boolean;
+ minZoom?: number;
+ maxZoom?: number;
+ defaultZoom?: number;
+ defaultPosition?: [number, number];
+ translateExtent?: CoordinateExtent;
+ preventScrolling?: boolean;
+ nodeExtent?: CoordinateExtent;
+ defaultMarkerColor?: string;
+ zoomOnScroll?: boolean;
+ zoomOnPinch?: boolean;
+ panOnScroll?: boolean;
+ 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) => void;
+ onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
+ edgeUpdaterRadius?: number;
+ noDragClassName?: string;
+ noWheelClassName?: string;
+ noPanClassName?: string;
+ fitView?: boolean;
+ fitViewOptions?: FitViewOptions;
+ connectOnClick?: boolean;
+ attributionPosition?: AttributionPosition;
+ proOptions?: ProOptions;
+}
+
+export type ReactFlowRefType = HTMLDivElement;
+
+export type GetMiniMapNodeAttribute = (node: Node) => string;
+
+export interface MiniMapProps extends HTMLAttributes {
+ nodeColor?: string | GetMiniMapNodeAttribute;
+ nodeStrokeColor?: string | GetMiniMapNodeAttribute;
+ nodeClassName?: string | GetMiniMapNodeAttribute;
+ nodeBorderRadius?: number;
+ nodeStrokeWidth?: number;
+ maskColor?: string;
+}
+
+export interface ControlProps extends HTMLAttributes {
+ showZoom?: boolean;
+ showFitView?: boolean;
+ showInteractive?: boolean;
+ fitViewOptions?: FitViewOptions;
+ onZoomIn?: () => void;
+ onZoomOut?: () => void;
+ onFitView?: () => void;
+ onInteractiveChange?: (interactiveStatus: boolean) => void;
+}
+
+export interface ControlButtonProps extends HTMLAttributes {}
+
+export interface BackgroundProps extends HTMLAttributes {
+ variant?: BackgroundVariant;
+ gap?: number;
+ color?: string;
+ size?: number;
+}
diff --git a/src/types/edges.ts b/src/types/edges.ts
new file mode 100644
index 00000000..2f83eac7
--- /dev/null
+++ b/src/types/edges.ts
@@ -0,0 +1,181 @@
+import { CSSProperties, HTMLAttributes, ReactNode } from 'react';
+import { Connection } from './general';
+import { HandleElement } from './handles';
+import { Node } from './nodes';
+import { Position } from './utils';
+
+// interface for the user edge items
+export interface Edge {
+ id: string;
+ type?: string;
+ source: string;
+ 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;
+ data?: T;
+ className?: string;
+ sourceNode?: Node;
+ targetNode?: Node;
+ selected?: boolean;
+ markerStart?: EdgeMarkerType;
+ markerEnd?: EdgeMarkerType;
+ zIndex?: number;
+}
+
+export type DefaultEdgeOptions = Omit<
+ Edge,
+ 'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'sourceNode' | 'targetNode'
+>;
+
+// props that get passed to a custom edge
+export interface EdgeProps {
+ 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;
+ curvature?: number;
+}
+
+export type BaseEdgeProps = Pick<
+ EdgeProps,
+ | 'label'
+ | 'labelStyle'
+ | 'labelShowBg'
+ | 'labelBgStyle'
+ | 'labelBgPadding'
+ | 'labelBgBorderRadius'
+ | 'style'
+ | 'markerStart'
+ | 'markerEnd'
+> & {
+ centerX: number;
+ centerY: number;
+ path: string;
+};
+
+export type EdgeMouseHandler = (event: React.MouseEvent, edge: Edge) => void;
+
+export interface WrapEdgeProps {
+ id: string;
+ className?: string;
+ type: string;
+ data?: T;
+ onClick?: EdgeMouseHandler;
+ onEdgeDoubleClick?: EdgeMouseHandler;
+ selected: boolean;
+ animated?: boolean;
+ label?: string | ReactNode;
+ labelStyle?: CSSProperties;
+ labelShowBg?: boolean;
+ labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
+ labelBgBorderRadius?: number;
+ style?: CSSProperties;
+ source: string;
+ target: string;
+ sourceHandleId: string | null;
+ targetHandleId: string | null;
+ sourceX: number;
+ sourceY: number;
+ targetX: number;
+ targetY: number;
+ sourcePosition: Position;
+ targetPosition: Position;
+ elementsSelectable?: boolean;
+ hidden?: boolean;
+ onEdgeUpdate: OnEdgeUpdateFunc;
+ onContextMenu?: EdgeMouseHandler;
+ onMouseEnter?: EdgeMouseHandler;
+ onMouseMove?: EdgeMouseHandler;
+ onMouseLeave?: EdgeMouseHandler;
+ edgeUpdaterRadius?: number;
+ onEdgeUpdateStart?: EdgeMouseHandler;
+ onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
+ markerStart?: EdgeMarkerType;
+ markerEnd?: EdgeMarkerType;
+}
+
+export interface EdgeSmoothStepProps extends EdgeProps {
+ borderRadius?: number;
+}
+
+export interface EdgeTextProps extends HTMLAttributes {
+ x: number;
+ y: number;
+ label?: string | ReactNode;
+ labelStyle?: CSSProperties;
+ labelShowBg?: boolean;
+ labelBgStyle?: CSSProperties;
+ labelBgPadding?: [number, number];
+ labelBgBorderRadius?: number;
+}
+
+export enum ConnectionLineType {
+ Bezier = 'default',
+ Straight = 'straight',
+ Step = 'step',
+ SmoothStep = 'smoothstep',
+ SimpleBezier = 'simplebezier',
+}
+
+export type ConnectionLineComponentProps = {
+ sourceX: number;
+ sourceY: number;
+ sourcePosition?: Position;
+ targetX: number;
+ targetY: number;
+ targetPosition?: Position;
+ connectionLineStyle?: CSSProperties;
+ connectionLineType: ConnectionLineType;
+ sourceNode?: Node;
+ sourceHandle?: HandleElement;
+};
+
+export type ConnectionLineComponent = React.ComponentType;
+
+export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void;
+
+export interface EdgeMarker {
+ type: MarkerType;
+ color?: string;
+ width?: number;
+ height?: number;
+ markerUnits?: string;
+ orient?: string;
+ strokeWidth?: number;
+}
+
+export type EdgeMarkerType = string | EdgeMarker;
+
+export enum MarkerType {
+ Arrow = 'arrow',
+ ArrowClosed = 'arrowclosed',
+}
diff --git a/src/types/general.ts b/src/types/general.ts
new file mode 100644
index 00000000..70bafaa9
--- /dev/null
+++ b/src/types/general.ts
@@ -0,0 +1,217 @@
+import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
+import { Selection as D3Selection, ZoomBehavior } from 'd3';
+
+import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
+import { NodeChange, EdgeChange } from './changes';
+import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes';
+import { Edge } from './edges';
+import { HandleType, StartHandle } from './handles';
+import { DefaultEdgeOptions } from '.';
+import { ReactFlowInstance } from './instance';
+
+export type NodeTypes = { [key: string]: ReactNode };
+export type EdgeTypes = NodeTypes;
+
+export type FitView = (fitViewOptions?: FitViewOptions) => void;
+
+export type Project = (position: XYPosition) => XYPosition;
+
+export type OnNodesChange = (nodes: NodeChange[]) => void;
+export type OnEdgesChange = (nodes: EdgeChange[]) => void;
+
+export type OnNodesDelete = (nodes: Node[]) => void;
+export type OnEdgesDelete = (edges: Edge[]) => void;
+
+export type OnMove = (event: MouseEvent | TouchEvent, viewport: Viewport) => void;
+export type OnMoveStart = OnMove;
+export type OnMoveEnd = OnMove;
+
+export type ZoomInOut = (options?: ViewportHelperFunctionOptions) => void;
+export type ZoomTo = (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void;
+export type GetZoom = () => number;
+export type GetViewport = () => Viewport;
+export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
+export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void;
+export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void;
+
+export type OnInit = (reactFlowInstance: ReactFlowInstance) => void;
+
+export interface Connection {
+ source: string | null;
+ target: string | null;
+ sourceHandle: string | null;
+ targetHandle: string | null;
+}
+
+export enum ConnectionMode {
+ Strict = 'strict',
+ Loose = 'loose',
+}
+
+export type OnConnect = (connection: Connection) => void;
+
+export type FitViewOptions = {
+ padding?: number;
+ includeHiddenNodes?: boolean;
+ minZoom?: number;
+ maxZoom?: number;
+ duration?: number;
+};
+
+export type OnConnectStartParams = {
+ nodeId: string | null;
+ handleId: string | null;
+ handleType: HandleType | null;
+};
+
+export type OnConnectStart = (event: ReactMouseEvent, params: OnConnectStartParams) => void;
+
+export type OnConnectStop = (event: MouseEvent) => void;
+
+export type OnConnectEnd = (event: MouseEvent) => void;
+
+export enum BackgroundVariant {
+ Lines = 'lines',
+ Dots = 'dots',
+}
+
+export type Viewport = {
+ x: number;
+ y: number;
+ zoom: number;
+};
+
+export type KeyCode = string | Array;
+
+export type SnapGrid = [number, number];
+
+export enum PanOnScrollMode {
+ Free = 'free',
+ Vertical = 'vertical',
+ Horizontal = 'horizontal',
+}
+
+export type ViewportHelperFunctionOptions = {
+ duration?: number;
+};
+
+export type SetCenterOptions = ViewportHelperFunctionOptions & {
+ zoom?: number;
+};
+
+export type FitBoundsOptions = ViewportHelperFunctionOptions & {
+ padding?: number;
+};
+
+export interface ViewportHelperFunctions {
+ zoomIn: ZoomInOut;
+ zoomOut: ZoomInOut;
+ zoomTo: ZoomTo;
+ getZoom: GetZoom;
+ setViewport: SetViewport;
+ getViewport: GetViewport;
+ fitView: FitView;
+ setCenter: SetCenter;
+ fitBounds: FitBounds;
+ project: Project;
+ initialized: boolean;
+}
+
+export type ReactFlowStore = {
+ width: number;
+ height: number;
+ transform: Transform;
+ nodeInternals: NodeInternals;
+ edges: Edge[];
+ selectedNodesBbox: Rect;
+ onNodesChange: OnNodesChange | null;
+ onEdgesChange: OnEdgesChange | null;
+ hasDefaultNodes: boolean;
+ hasDefaultEdges: boolean;
+
+ d3Zoom: ZoomBehavior | null;
+ d3Selection: D3Selection | null;
+ d3ZoomHandler: ((this: Element, event: any, d: unknown) => void) | undefined;
+ minZoom: number;
+ maxZoom: number;
+ translateExtent: CoordinateExtent;
+ nodeExtent: CoordinateExtent;
+
+ nodesSelectionActive: boolean;
+ userSelectionActive: boolean;
+
+ connectionNodeId: string | null;
+ connectionHandleId: string | null;
+ connectionHandleType: HandleType | null;
+ connectionPosition: XYPosition;
+ connectionMode: ConnectionMode;
+
+ snapToGrid: boolean;
+ snapGrid: SnapGrid;
+
+ nodesDraggable: boolean;
+ nodesConnectable: boolean;
+ elementsSelectable: boolean;
+
+ multiSelectionActive: boolean;
+
+ reactFlowVersion: string;
+
+ connectionStartHandle: StartHandle | null;
+
+ onConnect?: OnConnect;
+ onConnectStart?: OnConnectStart;
+ onConnectStop?: OnConnectStop;
+ onConnectEnd?: OnConnectEnd;
+
+ connectOnClick: boolean;
+ defaultEdgeOptions?: DefaultEdgeOptions;
+
+ fitViewOnInit: boolean;
+ fitViewOnInitDone: boolean;
+ fitViewOnInitOptions: FitViewOptions | undefined;
+
+ onNodesDelete?: OnNodesDelete;
+ onEdgesDelete?: OnEdgesDelete;
+};
+
+export type ReactFlowActions = {
+ setNodes: (nodes: Node[]) => void;
+ setEdges: (edges: Edge[]) => void;
+ setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
+ updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
+ updateNodePosition: (update: NodeDiffUpdate) => void;
+ resetSelectedElements: () => void;
+ unselectNodesAndEdges: () => void;
+ addSelectedNodes: (nodeIds: string[]) => void;
+ addSelectedEdges: (edgeIds: string[]) => void;
+ setMinZoom: (minZoom: number) => void;
+ setMaxZoom: (maxZoom: number) => void;
+ setTranslateExtent: (translateExtent: CoordinateExtent) => void;
+ setNodeExtent: (nodeExtent: CoordinateExtent) => void;
+ reset: () => void;
+};
+
+export type ReactFlowState = ReactFlowStore & ReactFlowActions;
+
+export type UpdateNodeInternals = (nodeId: string) => void;
+
+export type OnSelectionChangeParams = {
+ nodes: Node[];
+ edges: Edge[];
+};
+
+export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
+
+export type AttributionPosition =
+ | 'top-left'
+ | 'top-center'
+ | 'top-right'
+ | 'bottom-left'
+ | 'bottom-center'
+ | 'bottom-right';
+
+export type ProOptions = {
+ account: string;
+ hideAttribution: boolean;
+};
diff --git a/src/types/handles.ts b/src/types/handles.ts
new file mode 100644
index 00000000..37d1ea41
--- /dev/null
+++ b/src/types/handles.ts
@@ -0,0 +1,24 @@
+import { XYPosition, Position, Dimensions } from './utils';
+import { OnConnect, Connection } from './general';
+
+export type HandleType = 'source' | 'target';
+
+export interface HandleElement extends XYPosition, Dimensions {
+ id?: string | null;
+ position: Position;
+}
+
+export interface StartHandle {
+ nodeId: string;
+ type: HandleType;
+ handleId?: string | null;
+}
+
+export interface HandleProps {
+ type: HandleType;
+ position: Position;
+ isConnectable?: boolean;
+ onConnect?: OnConnect;
+ isValidConnection?: (connection: Connection) => boolean;
+ id?: string;
+}
diff --git a/src/types/index.ts b/src/types/index.ts
index d24a0c12..01cb0b69 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,447 +1,8 @@
-import React, { CSSProperties, MouseEvent as ReactMouseEvent, HTMLAttributes, ReactNode } from 'react';
-import { Selection as D3Selection, ZoomBehavior } from 'd3';
-
-export type ElementId = string;
-
-export type FlowElement = Node | Edge;
-
-export type Elements = Array>;
-
-export type Transform = [number, number, number];
-
-export enum Position {
- Left = 'left',
- Top = 'top',
- Right = 'right',
- Bottom = 'bottom',
-}
-
-export interface XYPosition {
- x: number;
- y: number;
-}
-
-export interface Dimensions {
- width: number;
- height: number;
-}
-
-export interface Rect extends Dimensions, XYPosition {}
-
-export interface Box extends XYPosition {
- x2: number;
- y2: number;
-}
-
-export type SnapGrid = [number, number];
-
-export interface Node {
- id: ElementId;
- position: XYPosition;
- type?: string;
- __rf?: any;
- data?: T;
- style?: CSSProperties;
- className?: string;
- targetPosition?: Position;
- sourcePosition?: Position;
- isHidden?: boolean;
- draggable?: boolean;
- selectable?: boolean;
- connectable?: boolean;
- dragHandle?: string;
-}
-
-export enum ArrowHeadType {
- Arrow = 'arrow',
- ArrowClosed = 'arrowclosed',
-}
-
-export interface Edge {
- id: ElementId;
- type?: string;
- source: ElementId;
- target: ElementId;
- sourceHandle?: ElementId | null;
- targetHandle?: ElementId | null;
- label?: string | ReactNode;
- labelStyle?: CSSProperties;
- labelShowBg?: boolean;
- labelBgStyle?: CSSProperties;
- labelBgPadding?: [number, number];
- labelBgBorderRadius?: number;
- style?: CSSProperties;
- animated?: boolean;
- arrowHeadType?: ArrowHeadType;
- isHidden?: boolean;
- data?: T;
- className?: string;
-}
-
-export enum BackgroundVariant {
- Lines = 'lines',
- Dots = 'dots',
-}
-
-export type HandleType = 'source' | 'target';
-
-export type NodeTypesType = { [key: string]: ReactNode };
-
-export type EdgeTypesType = NodeTypesType;
-
-export interface SelectionRect extends Rect {
- startX: number;
- startY: number;
- draw: boolean;
-}
-
-export interface WrapEdgeProps {
- id: ElementId;
- className?: string;
- type: string;
- data?: T;
- onClick?: (event: React.MouseEvent, edge: Edge) => void;
- onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
- selected: boolean;
- animated?: boolean;
- label?: string | ReactNode;
- labelStyle?: CSSProperties;
- labelShowBg?: boolean;
- labelBgStyle?: CSSProperties;
- labelBgPadding?: [number, number];
- labelBgBorderRadius?: number;
- style?: CSSProperties;
- arrowHeadType?: ArrowHeadType;
- source: ElementId;
- target: ElementId;
- sourceHandleId: ElementId | null;
- targetHandleId: ElementId | null;
- sourceX: number;
- sourceY: number;
- targetX: number;
- targetY: number;
- sourcePosition: Position;
- targetPosition: Position;
- elementsSelectable?: boolean;
- markerEndId?: string;
- isHidden?: boolean;
- handleEdgeUpdate: boolean;
- onConnectEdge: OnConnectFunc;
- onContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
- onMouseEnter?: (event: React.MouseEvent, edge: Edge) => void;
- onMouseMove?: (event: React.MouseEvent, edge: Edge) => void;
- onMouseLeave?: (event: React.MouseEvent, edge: Edge) => void;
- edgeUpdaterRadius?: number;
- onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void;
- onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
-}
-
-export interface EdgeProps {
- id: ElementId;
- source: ElementId;
- target: ElementId;
- 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;
- arrowHeadType?: ArrowHeadType;
- markerEndId?: string;
- data?: T;
- sourceHandleId?: ElementId | null;
- targetHandleId?: ElementId | null;
-}
-export interface EdgeSmoothStepProps extends EdgeProps