Merge branch 'main' into refactor/d3-drag
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
### Wire Your Ideas With React Flow!
|
||||
A highly customizable React component for building interactive graphs and node-based editors.
|
||||
|
||||
[🚀 Getting Started](https://reactflow.dev/docs/getting-started/installation) | [📖 Documentation](https://reactflow.dev/docs/api/react-flow-props) | [📺 Examples](https://reactflow.dev/docs/examples/overview) | [☎️ Discord](https://discord.gg/6ZcAMJmJ) | [💎 React Flow Pro](https://pro.reactflow.dev/pricing)
|
||||
[🚀 Getting Started](https://reactflow.dev/docs/getting-started/installation) | [📖 Documentation](https://reactflow.dev/docs/api/react-flow-props) | [📺 Examples](https://reactflow.dev/docs/examples/overview) | [☎️ Discord](https://discord.gg/RVmnytFmGW) | [💎 React Flow Pro](https://pro.reactflow.dev/pricing)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ A highly customizable React component for building interactive graphs and node-b
|
||||
## Attribution
|
||||
|
||||
React Flow includes a small attribution that links to the React Flow website. Per default the attribution is displayed in the bottom right corner. With the attribution we want to achieve that companies who are using React Flow in commercial applications finance the development of the library.
|
||||
We expect users who are using React Flow commercially to subscribe to React Flow Pro if they want to remove the attribution. In non-commercial applications you may hide the attribution without subscribing but are welcome to [sponsor us on Github](https://github.com/sponsors/wbkd).
|
||||
We expect users who are using React Flow commercially to subscribe to [React Flow Pro](https://pro.reactflow.dev/pricing) if they want to remove the attribution. In non-commercial applications you may hide the attribution without subscribing but are welcome to [sponsor us on Github](https://github.com/sponsors/wbkd).
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('Basic Flow Rendering', () => {
|
||||
});
|
||||
|
||||
it('selects a node by click', () => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('deselects node', () => {
|
||||
@@ -49,19 +49,20 @@ describe('Basic Flow Rendering', () => {
|
||||
it('selects one node with a selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 1000, 1, { which: 1, force: true })
|
||||
.trigger('mousemove', 1, 200, { which: 1 })
|
||||
.trigger('mouseup', 1, 200, { force: true })
|
||||
.get('.react-flow__node')
|
||||
.first()
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__node')
|
||||
.last()
|
||||
.should('have.not.class', 'selected')
|
||||
.get('.react-flow__nodesselection-rect');
|
||||
.trigger('mousedown', 1000, 50, { which: 1, force: true })
|
||||
.trigger('mousemove', 1, 400, { which: 1 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 1, 200, { force: true });
|
||||
|
||||
cy.get('body').type('{shift}', { release: true });
|
||||
cy.get('.react-flow__node').eq(3).should('have.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__node').eq(0).should('have.not.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__nodesselection-rect');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true, force: true });
|
||||
});
|
||||
|
||||
it('selects all nodes', () => {
|
||||
@@ -70,7 +71,9 @@ describe('Basic Flow Rendering', () => {
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 'topRight', { which: 1, force: true })
|
||||
.trigger('mousemove', 'bottomLeft', { which: 1 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 'bottomLeft', { force: true })
|
||||
.wait(50)
|
||||
.get('.react-flow__node')
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__nodesselection-rect');
|
||||
@@ -96,13 +99,13 @@ describe('Basic Flow Rendering', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('removes a node', () => {
|
||||
cy.get('.react-flow__node').contains('Node 2').click();
|
||||
cy.get('body').type('{backspace}');
|
||||
|
||||
cy.get('.react-flow__node').should('have.length', 3);
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes a node', () => {
|
||||
// cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
|
||||
// cy.get('html').type('{backspace}').wait(100);
|
||||
// cy.get('.react-flow__node').should('have.length', 3);
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
@@ -114,17 +117,19 @@ describe('Basic Flow Rendering', () => {
|
||||
.contains('Node 4')
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove', { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 2);
|
||||
});
|
||||
|
||||
it('removes an edge', () => {
|
||||
cy.get('.react-flow__edge:first').click();
|
||||
cy.get('body').type('{backspace}');
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes an edge', () => {
|
||||
// cy.get('.react-flow__edge:first').click();
|
||||
// cy.get('body').type('{backspace}');
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
|
||||
it('drags the pane', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
@@ -135,6 +140,7 @@ describe('Basic Flow Rendering', () => {
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||
.trigger('mousemove', 'bottomLeft')
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
@@ -148,6 +154,7 @@ describe('Basic Flow Rendering', () => {
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||
|
||||
@@ -38,8 +38,9 @@ describe('Controls Testing', () => {
|
||||
cy.window().then((win) => {
|
||||
cy.get('.react-flow__renderer')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||
.trigger('mousemove', 'bottomLeft')
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.trigger('mousemove', 10, 400)
|
||||
.wait(50)
|
||||
.trigger('mouseup', 10, 400, { force: true, view: win })
|
||||
.then(() => {
|
||||
const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
@@ -60,7 +61,7 @@ describe('Controls Testing', () => {
|
||||
|
||||
it('uses interactive control - not interactive', () => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
cy.get('.react-flow__renderer').click('bottomRight');
|
||||
cy.get('.react-flow__pane').click('topLeft');
|
||||
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__controls-interactive')
|
||||
@@ -74,7 +75,7 @@ describe('Controls Testing', () => {
|
||||
cy.get('.react-flow__controls-interactive')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,6 +125,7 @@ describe('Interaction Flow Rendering', () => {
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('wheel', 'topLeft', { deltaY: 200 })
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||
@@ -141,6 +142,7 @@ describe('Interaction Flow Rendering', () => {
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.dblclick()
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||
|
||||
@@ -12,6 +12,8 @@ describe('Minimap Testing', () => {
|
||||
const paneNodes = Cypress.$('.react-flow__node').length;
|
||||
const minimapNodes = Cypress.$('.react-flow__minimap-node').length;
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
expect(paneNodes).equal(minimapNodes);
|
||||
});
|
||||
|
||||
@@ -21,6 +23,7 @@ describe('Minimap Testing', () => {
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const viewBoxAfterZoom = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
@@ -54,6 +57,7 @@ describe('Minimap Testing', () => {
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||
.trigger('mousemove', 'bottomLeft')
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
const viewBoxAfterDrag = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
|
||||
Cypress.Commands.add('drag', (selector, { x, y }) => {
|
||||
return cy.get(selector)
|
||||
return cy
|
||||
.get(selector)
|
||||
.trigger('mousedown', { which: 1 })
|
||||
.trigger('mousemove', { clientX: x, clientY: y })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { force: true });
|
||||
});
|
||||
|
||||
Generated
+4505
-7039
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,9 @@
|
||||
"react": "file:../node_modules/react",
|
||||
"react-dom": "file:../node_modules/react-dom",
|
||||
"react-flow-renderer": "file:../",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-scripts": "5.0.0",
|
||||
"typescript": "^4.5.5"
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@types/dagre": "^0.7.47",
|
||||
"@types/localforage": "0.0.34",
|
||||
"@types/react": "file:../node_modules/@types/react",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@types/react-router-dom": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ const initialEdges: Edge[] = [
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const defaultEdgeOptions = { zIndex: 0 };
|
||||
|
||||
const BasicFlow = () => {
|
||||
const instance = useReactFlow();
|
||||
|
||||
@@ -60,10 +62,10 @@ const BasicFlow = () => {
|
||||
onNodeClick={onNodeClick}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
className="react-flow-basic-example"
|
||||
defaultZoom={1.5}
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import React, { MouseEvent, useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
@@ -189,7 +189,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
|
||||
const onPaneClick = useCallback(
|
||||
(evt) =>
|
||||
(evt: MouseEvent) =>
|
||||
setNodes((nds) =>
|
||||
nds.concat({
|
||||
id: getId(),
|
||||
@@ -198,7 +198,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
data: null,
|
||||
})
|
||||
),
|
||||
[project]
|
||||
[project, setNodes]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, MouseEvent } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
@@ -36,7 +36,7 @@ const UseZoomPanHelperFlow = () => {
|
||||
const { project, setCenter, zoomIn, zoomOut, fitView, addNodes, setNodes: setNodesHook } = useReactFlow();
|
||||
|
||||
const onPaneClick = useCallback(
|
||||
(evt) => {
|
||||
(evt: MouseEvent) => {
|
||||
const projectedPosition = project({ x: evt.clientX, y: evt.clientY - 40 });
|
||||
|
||||
setNodes((nds) =>
|
||||
@@ -53,8 +53,8 @@ const UseZoomPanHelperFlow = () => {
|
||||
);
|
||||
|
||||
const onNodeClick = useCallback(
|
||||
(_, element) => {
|
||||
const { x, y } = element.position;
|
||||
(_: MouseEvent, node: Node) => {
|
||||
const { x, y } = node.position;
|
||||
setCenter(x, y, { zoom: 1, duration: 1200 });
|
||||
},
|
||||
[setCenter]
|
||||
@@ -72,9 +72,7 @@ const UseZoomPanHelperFlow = () => {
|
||||
addNodes(newNode);
|
||||
}, [addNodes]);
|
||||
|
||||
const onResetNodes = useCallback(() => {
|
||||
setNodesHook(initialNodes);
|
||||
}, [setNodesHook]);
|
||||
const onResetNodes = useCallback(() => setNodesHook(initialNodes), [setNodesHook]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, CSSProperties } from 'react';
|
||||
import { useCallback, CSSProperties, MouseEvent } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
NodeTypes,
|
||||
@@ -44,7 +44,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
const { project } = useReactFlow();
|
||||
|
||||
const onPaneClick = useCallback(
|
||||
(evt) =>
|
||||
(evt: MouseEvent) =>
|
||||
setNodes((nds) =>
|
||||
nds.concat({
|
||||
id: getId(),
|
||||
@@ -54,7 +54,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
sourcePosition: Position.Right,
|
||||
})
|
||||
),
|
||||
[project]
|
||||
[project, setNodes]
|
||||
);
|
||||
|
||||
const toggleHandleCount = useCallback(() => {
|
||||
@@ -63,7 +63,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
return { ...node, data: { ...node.data, handleCount: node.data?.handleCount === 1 ? 2 : 1 } };
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
}, [setNodes]);
|
||||
|
||||
const toggleHandlePosition = useCallback(() => {
|
||||
setNodes((nds) =>
|
||||
@@ -71,7 +71,7 @@ const UpdateNodeInternalsFlow = () => {
|
||||
return { ...node, data: { ...node.data, handlePosition: node.data?.handlePosition === 0 ? 1 : 0 } };
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
}, [setNodes]);
|
||||
|
||||
const updateNode = useCallback(() => updateNodeInternals('1'), [updateNodeInternals]);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import Basic from './Basic';
|
||||
import ControlledUncontrolled from './ControlledUncontrolled';
|
||||
import CustomConnectionLine from './CustomConnectionLine';
|
||||
@@ -187,7 +188,10 @@ const Header = () => {
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<Header />
|
||||
<Routes>
|
||||
@@ -195,6 +199,5 @@ ReactDOM.render(
|
||||
<Route path={route.path} key={route.path} element={<route.component />} />
|
||||
))}
|
||||
</Routes>
|
||||
</BrowserRouter>,
|
||||
document.getElementById('root')
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"internal": true,
|
||||
"type": "module",
|
||||
"main": "../dist/nocss/index.js",
|
||||
"module": "../dist/nocss/index.js",
|
||||
"types": "../dist/nocss/index.d.ts"
|
||||
"main": "../dist/nocss/umd/index.js",
|
||||
"module": "../dist/nocss/esm/index.js",
|
||||
"types": "../dist/nocss/esm/index.d.ts"
|
||||
}
|
||||
|
||||
Generated
+2463
-1160
File diff suppressed because it is too large
Load Diff
+33
-24
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-flow-renderer",
|
||||
"version": "10.1.0",
|
||||
"version": "10.2.2",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
@@ -13,7 +13,13 @@
|
||||
"import": "./dist/esm/index.js",
|
||||
"require": "./dist/umd/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
"./nocss": {
|
||||
"import": "./dist/nocss/esm/index.js",
|
||||
"require": "./dist/nocss/umd/index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./dist/style.css": "./dist/style.css",
|
||||
"./dist/theme-default.css": "./dist/theme-default.css"
|
||||
},
|
||||
"types": "dist/esm/index.d.ts",
|
||||
"license": "MIT",
|
||||
@@ -22,11 +28,12 @@
|
||||
"url": "https://github.com/wbkd/react-flow.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup -c --environment NODE_ENV:production && postcss src/*.css --dir dist",
|
||||
"build": "rimraf dist && rollup -c --environment NODE_ENV:production && npm run css",
|
||||
"build:test": "rimraf dist && rollup -c --environment NODE_ENV:test && npm run css",
|
||||
"start": "rollup -w -c",
|
||||
"start:examples": "cd example && npm start",
|
||||
"build:examples": "cd example && npm run build",
|
||||
"start:testserver": "npm run build && npm run start:examples",
|
||||
"start:testserver": "npm run build:test && npm run start:examples",
|
||||
"build:dev": "npm install && npm run build && cd example && npm install && npm run build",
|
||||
"dev:wait": "start-server-and-test start:testserver http-get://localhost:3000",
|
||||
"test": "BROWSER=none npm run dev:wait test:chrome",
|
||||
@@ -37,41 +44,43 @@
|
||||
"cy:open": "cypress open",
|
||||
"release": "npm run test && release-it",
|
||||
"release:notest": "release-it",
|
||||
"release:next": "release-it --preRelease=next --no-git"
|
||||
"release:next": "release-it --preRelease=next --no-git",
|
||||
"css": "postcss src/*.css --dir dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@babel/runtime": "^7.17.9",
|
||||
"classcat": "^5.0.3",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"zustand": "^3.7.1"
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/core": "^7.17.10",
|
||||
"@babel/plugin-transform-runtime": "^7.17.10",
|
||||
"@babel/preset-env": "^7.17.10",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@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-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.1",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@types/d3": "^7.1.0",
|
||||
"@types/react": "^17.0.40",
|
||||
"@types/react": "^18.0.8",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@types/resize-observer-browser": "^0.1.7",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"autoprefixer": "^10.4.5",
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"cypress": "^9.5.1",
|
||||
"postcss": "^8.4.8",
|
||||
"cypress": "^9.6.0",
|
||||
"postcss": "^8.4.13",
|
||||
"postcss-cli": "^9.1.0",
|
||||
"postcss-nested": "^5.0.6",
|
||||
"prettier": "2.5.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"release-it": "^14.12.5",
|
||||
"prettier": "^2.6.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"release-it": "^14.14.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup": "^2.71.0",
|
||||
"rollup-plugin-livereload": "^2.0.5",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-serve": "^1.1.0",
|
||||
@@ -79,11 +88,11 @@
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"typescript": "^4.6.2"
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16 || 17",
|
||||
"react-dom": "16 || 17"
|
||||
"react": "16 || 17 || 18",
|
||||
"react-dom": "16 || 17 || 18"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
+29
-14
@@ -11,7 +11,7 @@ import { terser } from 'rollup-plugin-terser';
|
||||
import pkg from './package.json';
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const isTesting = process.env.NODE_ENV === 'testing';
|
||||
const isTesting = process.env.NODE_ENV === 'test';
|
||||
const processEnv = isProd || isTesting ? 'production' : 'development';
|
||||
|
||||
const defaultOutputOptions = {
|
||||
@@ -20,6 +20,18 @@ const defaultOutputOptions = {
|
||||
sourcemap: true,
|
||||
};
|
||||
|
||||
const globals = {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
classcat: 'cc',
|
||||
'd3-selection': 'd3',
|
||||
'd3-zoom': 'd3',
|
||||
'react-draggable': 'ReactDraggable',
|
||||
zustand: 'zustand',
|
||||
'zustand/shallow': 'zustandShallow',
|
||||
'zustand/context': 'zustandContext',
|
||||
};
|
||||
|
||||
export const baseConfig = ({ outputOptions = {}, injectCSS = true } = {}) => {
|
||||
const output = {
|
||||
...defaultOutputOptions,
|
||||
@@ -97,30 +109,33 @@ export const baseConfig = ({ outputOptions = {}, injectCSS = true } = {}) => {
|
||||
|
||||
export default isProd && !isTesting
|
||||
? [
|
||||
// esm build
|
||||
baseConfig(),
|
||||
// umd build
|
||||
baseConfig({
|
||||
outputOptions: {
|
||||
dir: 'dist/umd',
|
||||
format: 'umd',
|
||||
exports: 'named',
|
||||
name: 'ReactFlow',
|
||||
globals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
classcat: 'cc',
|
||||
'd3-selection': 'd3',
|
||||
'd3-zoom': 'd3',
|
||||
'd3-drag': 'd3',
|
||||
'react-draggable': 'ReactDraggable',
|
||||
zustand: 'zustand',
|
||||
'zustand/shallow': 'zustandShallow',
|
||||
'zustand/context': 'zustandContext',
|
||||
},
|
||||
globals,
|
||||
},
|
||||
}),
|
||||
// nocsss esm build
|
||||
baseConfig({
|
||||
outputOptions: {
|
||||
dir: 'dist/nocss',
|
||||
dir: 'dist/nocss/esm',
|
||||
},
|
||||
injectCSS: false,
|
||||
}),
|
||||
// nocsss umd build
|
||||
baseConfig({
|
||||
outputOptions: {
|
||||
dir: 'dist/nocss/umd',
|
||||
format: 'umd',
|
||||
exports: 'named',
|
||||
name: 'ReactFlow',
|
||||
globals,
|
||||
},
|
||||
|
||||
injectCSS: false,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useMemo, FC } from 'react';
|
||||
import React, { memo, FC, useEffect, useState, useRef } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
@@ -20,9 +20,16 @@ const Background: FC<BackgroundProps> = ({
|
||||
style,
|
||||
className,
|
||||
}) => {
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
const [patternId, setPatternId] = useState<string | null>(null);
|
||||
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)}`, []);
|
||||
|
||||
useEffect(() => {
|
||||
// when there are multiple flows on a page we need to make sure that every background gets its own pattern.
|
||||
const bgs = document.querySelectorAll('.react-flow__background');
|
||||
const index = Array.from(bgs).findIndex((bg) => bg === ref.current);
|
||||
setPatternId(`pattern-${index}`);
|
||||
}, []);
|
||||
|
||||
const bgClasses = cc(['react-flow__background', 'react-flow__container', className]);
|
||||
const scaledGap = gap * scale;
|
||||
@@ -41,18 +48,23 @@ const Background: FC<BackgroundProps> = ({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<pattern
|
||||
id={patternId}
|
||||
x={xOffset}
|
||||
y={yOffset}
|
||||
width={scaledGap}
|
||||
height={scaledGap}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
{path}
|
||||
</pattern>
|
||||
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
|
||||
{patternId && (
|
||||
<>
|
||||
<pattern
|
||||
id={patternId}
|
||||
x={xOffset}
|
||||
y={yOffset}
|
||||
width={scaledGap}
|
||||
height={scaledGap}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
{path}
|
||||
</pattern>
|
||||
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useCallback, FC, useEffect, useState } from 'react';
|
||||
import React, { memo, useCallback, FC, useEffect, useState, PropsWithChildren } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
@@ -12,7 +12,7 @@ import UnlockIcon from './Icons/Unlock';
|
||||
|
||||
import { ControlProps, ControlButtonProps, ReactFlowState } from '../../types';
|
||||
|
||||
export const ControlButton: FC<ControlButtonProps> = ({ children, className, ...rest }) => (
|
||||
export const ControlButton: FC<PropsWithChildren<ControlButtonProps>> = ({ children, className, ...rest }) => (
|
||||
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
@@ -20,7 +20,7 @@ export const ControlButton: FC<ControlButtonProps> = ({ children, className, ...
|
||||
|
||||
const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable;
|
||||
|
||||
const Controls: FC<ControlProps> = ({
|
||||
const Controls: FC<PropsWithChildren<ControlProps>> = ({
|
||||
style,
|
||||
showZoom = true,
|
||||
showFitView = true,
|
||||
|
||||
@@ -2,7 +2,9 @@ import React, { FC, PropsWithChildren } from 'react';
|
||||
|
||||
import { Provider, createStore } from '../../store';
|
||||
|
||||
const ReactFlowProvider: FC<PropsWithChildren<{}>> = ({ children }) => <Provider createStore={createStore}>{children}</Provider>;
|
||||
const ReactFlowProvider: FC<PropsWithChildren<{}>> = ({ children }) => (
|
||||
<Provider createStore={createStore}>{children}</Provider>
|
||||
);
|
||||
|
||||
ReactFlowProvider.displayName = 'ReactFlowProvider';
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ type AttributionProps = {
|
||||
|
||||
function Attribution({ proOptions, position = 'bottom-right' }: AttributionProps) {
|
||||
if (
|
||||
(proOptions?.account === 'paid-sponsor' ||
|
||||
(proOptions?.account === 'paid-pro' ||
|
||||
proOptions?.account === 'paid-sponsor' ||
|
||||
proOptions?.account === 'paid-enterprise' ||
|
||||
proOptions?.account === 'paid-custom') &&
|
||||
proOptions?.hideAttribution
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { memo, useRef, useState, useEffect, FC } from 'react';
|
||||
import React, { memo, useRef, useState, useEffect, FC, PropsWithChildren } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { EdgeTextProps, Rect } from '../../types';
|
||||
|
||||
const EdgeText: FC<EdgeTextProps> = ({
|
||||
const EdgeText: FC<PropsWithChildren<EdgeTextProps>> = ({
|
||||
x,
|
||||
y,
|
||||
label,
|
||||
|
||||
@@ -140,7 +140,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const handleEdgeUpdater = useCallback(
|
||||
(event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
||||
const nodeId = isSourceHandle ? target : source;
|
||||
const handleId = isSourceHandle ? targetHandleId : sourceHandleId;
|
||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||
const handleType = isSourceHandle ? 'target' : 'source';
|
||||
const isValidConnection = () => true;
|
||||
const isTarget = isSourceHandle;
|
||||
|
||||
@@ -24,9 +24,7 @@ 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),
|
||||
selectedNodes: Array.from(s.nodeInternals.values()).filter((n) => n.selected),
|
||||
updateNodePosition: s.updateNodePosition,
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,10 @@ export function useMarkerSymbol(type: MarkerType) {
|
||||
const symbolExists = MarkerSymbols.hasOwnProperty(type);
|
||||
|
||||
if (!symbolExists) {
|
||||
console.warn(`marker type "${type}" doesn't exist.`);
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(`[React Flow]: Marker type "${type}" doesn't exist. Help: https://reactflow.dev/error#900`);
|
||||
}
|
||||
return () => null;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,12 @@ import {
|
||||
OnEdgeUpdateFunc,
|
||||
HandleType,
|
||||
ReactFlowState,
|
||||
EdgeTypesWrapped,
|
||||
} from '../../types';
|
||||
import useVisibleEdges from '../../hooks/useVisibleEdges';
|
||||
|
||||
interface EdgeRendererProps {
|
||||
edgeTypes: any;
|
||||
edgeTypes: EdgeTypesWrapped;
|
||||
connectionLineType: ConnectionLineType;
|
||||
connectionLineStyle?: CSSProperties;
|
||||
connectionLineComponent?: ConnectionLineComponent;
|
||||
@@ -36,6 +37,7 @@ interface EdgeRendererProps {
|
||||
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
|
||||
edgeUpdaterRadius?: number;
|
||||
noPanClassName?: string;
|
||||
elevateEdgesOnSelect: boolean;
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
@@ -64,7 +66,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
connectionMode,
|
||||
nodeInternals,
|
||||
} = useStore(selector, shallow);
|
||||
const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals);
|
||||
const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals, props.elevateEdgesOnSelect);
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
@@ -106,12 +108,22 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const targetPosition = targetHandle?.position || Position.Top;
|
||||
|
||||
if (!sourceHandle) {
|
||||
console.warn(`couldn't create edge for source handle id: ${edge.sourceHandle}; edge id: ${edge.id}`);
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
`[React Flow]: Couldn't create edge for source handle id: ${edge.sourceHandle}; edge id: ${edge.id}. Help: https://reactflow.dev/error#800`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!targetHandle) {
|
||||
console.warn(`couldn't create edge for target handle id: ${edge.targetHandle}; edge id: ${edge.id}`);
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
`[React Flow]: Couldn't create edge for target handle id: ${edge.targetHandle}; edge id: ${edge.id}. Help: https://reactflow.dev/error#800`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import wrapEdge from '../../components/Edges/wrapEdge';
|
||||
import {
|
||||
EdgeProps,
|
||||
EdgeTypes,
|
||||
EdgeTypesWrapped,
|
||||
HandleElement,
|
||||
NodeHandleBounds,
|
||||
NodeInternals,
|
||||
@@ -14,10 +15,10 @@ import {
|
||||
} from '../../types';
|
||||
import { rectToBox } from '../../utils';
|
||||
|
||||
export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypes;
|
||||
export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped;
|
||||
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypes {
|
||||
const standardTypes: EdgeTypes = {
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypesWrapped {
|
||||
const standardTypes: EdgeTypesWrapped = {
|
||||
default: wrapEdge((edgeTypes.default || BezierEdge) as ComponentType<EdgeProps>),
|
||||
straight: wrapEdge((edgeTypes.bezier || StraightEdge) as ComponentType<EdgeProps>),
|
||||
step: wrapEdge((edgeTypes.step || StepEdge) as ComponentType<EdgeProps>),
|
||||
@@ -25,8 +26,8 @@ export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypes {
|
||||
simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdge) as ComponentType<EdgeProps>),
|
||||
};
|
||||
|
||||
const wrappedTypes = {} as EdgeTypes;
|
||||
const specialTypes: EdgeTypes = Object.keys(edgeTypes)
|
||||
const wrappedTypes = {} as EdgeTypesWrapped;
|
||||
const specialTypes: EdgeTypesWrapped = Object.keys(edgeTypes)
|
||||
.filter((k) => !['default', 'bezier'].includes(k))
|
||||
.reduce((res, key) => {
|
||||
res[key] = wrapEdge((edgeTypes[key] || BezierEdge) as ComponentType<EdgeProps>);
|
||||
|
||||
@@ -5,11 +5,12 @@ import NodeRenderer from '../NodeRenderer';
|
||||
import EdgeRenderer from '../EdgeRenderer';
|
||||
import Viewport from '../Viewport';
|
||||
import useOnInitHandler from '../../hooks/useOnInitHandler';
|
||||
import { NodeTypes, EdgeTypes, ConnectionLineType, KeyCode, ReactFlowProps } from '../../types';
|
||||
import { NodeTypesWrapped, EdgeTypesWrapped, ConnectionLineType, KeyCode, ReactFlowProps } from '../../types';
|
||||
|
||||
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
|
||||
nodeTypes: NodeTypes;
|
||||
edgeTypes: EdgeTypes;
|
||||
export interface GraphViewProps
|
||||
extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges' | 'nodeTypes' | 'edgeTypes'> {
|
||||
nodeTypes: NodeTypesWrapped;
|
||||
edgeTypes: EdgeTypesWrapped;
|
||||
selectionKeyCode: KeyCode | null;
|
||||
deleteKeyCode: KeyCode | null;
|
||||
multiSelectionKeyCode: KeyCode | null;
|
||||
@@ -81,6 +82,7 @@ const GraphView = ({
|
||||
noDragClassName,
|
||||
noWheelClassName,
|
||||
noPanClassName,
|
||||
elevateEdgesOnSelect,
|
||||
}: GraphViewProps) => {
|
||||
useOnInitHandler(onInit);
|
||||
|
||||
@@ -134,6 +136,7 @@ const GraphView = ({
|
||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||
defaultMarkerColor={defaultMarkerColor}
|
||||
noPanClassName={noPanClassName}
|
||||
elevateEdgesOnSelect={!!elevateEdgesOnSelect}
|
||||
/>
|
||||
<NodeRenderer
|
||||
nodeTypes={nodeTypes}
|
||||
|
||||
@@ -3,10 +3,10 @@ import shallow from 'zustand/shallow';
|
||||
|
||||
import useVisibleNodes from '../../hooks/useVisibleNodes';
|
||||
import { useStore } from '../../store';
|
||||
import { Node, NodeTypes, Position, ReactFlowState, WrapNodeProps } from '../../types';
|
||||
import { Node, NodeTypesWrapped, Position, ReactFlowState, WrapNodeProps } from '../../types';
|
||||
|
||||
interface NodeRendererProps {
|
||||
nodeTypes: NodeTypes;
|
||||
nodeTypes: NodeTypesWrapped;
|
||||
selectNodesOnDrag: boolean;
|
||||
onNodeClick?: (event: MouseEvent, element: Node) => void;
|
||||
onNodeDoubleClick?: (event: MouseEvent, element: Node) => void;
|
||||
@@ -66,8 +66,13 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
{nodes.map((node) => {
|
||||
const nodeType = node.type || 'default';
|
||||
|
||||
if (!props.nodeTypes[nodeType]) {
|
||||
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`);
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (!props.nodeTypes[nodeType]) {
|
||||
console.warn(
|
||||
`[React Flow]: Node type "${nodeType}" not found. Using fallback type "default". Help: https://reactflow.dev/error#300`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
||||
|
||||
@@ -5,20 +5,20 @@ import InputNode from '../../components/Nodes/InputNode';
|
||||
import OutputNode from '../../components/Nodes/OutputNode';
|
||||
import GroupNode from '../../components/Nodes/GroupNode';
|
||||
import wrapNode from '../../components/Nodes/wrapNode';
|
||||
import { NodeTypes, NodeProps } from '../../types';
|
||||
import { NodeTypes, NodeProps, NodeTypesWrapped } from '../../types';
|
||||
|
||||
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypes;
|
||||
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped;
|
||||
|
||||
export function createNodeTypes(nodeTypes: NodeTypes): NodeTypes {
|
||||
const standardTypes: NodeTypes = {
|
||||
export function createNodeTypes(nodeTypes: NodeTypes): NodeTypesWrapped {
|
||||
const standardTypes: NodeTypesWrapped = {
|
||||
input: wrapNode((nodeTypes.input || InputNode) as ComponentType<NodeProps>),
|
||||
default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType<NodeProps>),
|
||||
output: wrapNode((nodeTypes.output || OutputNode) as ComponentType<NodeProps>),
|
||||
group: wrapNode((nodeTypes.group || GroupNode) as ComponentType<NodeProps>),
|
||||
};
|
||||
|
||||
const wrappedTypes = {} as NodeTypes;
|
||||
const specialTypes: NodeTypes = Object.keys(nodeTypes)
|
||||
const wrappedTypes = {} as NodeTypesWrapped;
|
||||
const specialTypes: NodeTypesWrapped = Object.keys(nodeTypes)
|
||||
.filter((k) => !['input', 'default', 'output', 'group'].includes(k))
|
||||
.reduce((res, key) => {
|
||||
res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType<NodeProps>);
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
ConnectionLineType,
|
||||
ConnectionMode,
|
||||
EdgeTypes,
|
||||
EdgeTypesWrapped,
|
||||
NodeTypes,
|
||||
NodeTypesWrapped,
|
||||
PanOnScrollMode,
|
||||
ReactFlowProps,
|
||||
ReactFlowRefType,
|
||||
@@ -21,7 +23,7 @@ import {
|
||||
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
||||
import GraphView from '../GraphView';
|
||||
import { createNodeTypes } from '../NodeRenderer/utils';
|
||||
import injectStyle, { useNodeOrEdgeTypes } from './utils';
|
||||
import { injectStyle, useNodeOrEdgeTypes } from './utils';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
if (__INJECT_STYLES__) {
|
||||
@@ -29,13 +31,13 @@ if (__INJECT_STYLES__) {
|
||||
injectStyle(theme as unknown as string);
|
||||
}
|
||||
|
||||
const defaultNodeTypes = {
|
||||
const defaultNodeTypes: NodeTypes = {
|
||||
input: InputNode,
|
||||
default: DefaultNode,
|
||||
output: OutputNode,
|
||||
};
|
||||
|
||||
const defaultEdgeTypes = {
|
||||
const defaultEdgeTypes: EdgeTypes = {
|
||||
default: BezierEdge,
|
||||
straight: StraightEdge,
|
||||
step: StepEdge,
|
||||
@@ -135,12 +137,13 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
attributionPosition,
|
||||
proOptions,
|
||||
defaultEdgeOptions,
|
||||
elevateEdgesOnSelect = false,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const nodeTypesParsed = useNodeOrEdgeTypes(nodeTypes, createNodeTypes) as NodeTypes;
|
||||
const edgeTypesParsed = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes) as EdgeTypes;
|
||||
const nodeTypesWrapped = useNodeOrEdgeTypes(nodeTypes, createNodeTypes) as NodeTypesWrapped;
|
||||
const edgeTypesWrapped = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes) as EdgeTypesWrapped;
|
||||
const reactFlowClasses = cc(['react-flow', className]);
|
||||
|
||||
return (
|
||||
@@ -161,8 +164,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
nodeTypes={nodeTypesParsed}
|
||||
edgeTypes={edgeTypesParsed}
|
||||
nodeTypes={nodeTypesWrapped}
|
||||
edgeTypes={edgeTypesWrapped}
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineComponent={connectionLineComponent}
|
||||
@@ -202,6 +205,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
noDragClassName={noDragClassName}
|
||||
noWheelClassName={noWheelClassName}
|
||||
noPanClassName={noPanClassName}
|
||||
elevateEdgesOnSelect={elevateEdgesOnSelect}
|
||||
/>
|
||||
<StoreUpdater
|
||||
nodes={nodes}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { EdgeTypes, NodeTypes } from '../../types';
|
||||
import { EdgeTypes, EdgeTypesWrapped, NodeTypes, NodeTypesWrapped } 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: NodeTypes, createTypes: CreateNodeTypes): NodeTypesWrapped;
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: EdgeTypes, createTypes: CreateEdgeTypes): EdgeTypesWrapped;
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any {
|
||||
const typesKeysRef = useRef<string[] | null>(null);
|
||||
|
||||
@@ -16,7 +16,7 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any
|
||||
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."
|
||||
"[React Flow]: It looks like you have 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. Help: https://reactflow.dev/error#200"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any
|
||||
return typesParsed;
|
||||
}
|
||||
|
||||
export default function injectStyle(css: string): void {
|
||||
export function injectStyle(css: string): void {
|
||||
if (!css || typeof document === 'undefined') return;
|
||||
|
||||
const head = document.head || document.getElementsByTagName('head')[0];
|
||||
|
||||
@@ -16,8 +16,13 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
|
||||
|
||||
const size = getDimensions(rendererNode.current);
|
||||
|
||||
if (size.height === 0 || size.width === 0) {
|
||||
console.warn('The React Flow parent container needs a width and a height to render the graph.');
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (size.height === 0 || size.width === 0) {
|
||||
console.warn(
|
||||
'[React Flow]: The React Flow parent container needs a width and a height to render the graph. Help: https://reactflow.dev/error#400'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
store.setState({ width: size.width || 500, height: size.height || 500 });
|
||||
|
||||
@@ -7,13 +7,19 @@ import { isNumeric } from '../utils';
|
||||
|
||||
const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
|
||||
|
||||
function groupEdgesByZLevel(edges: Edge[], nodeInternals: NodeInternals) {
|
||||
function groupEdgesByZLevel(edges: Edge[], nodeInternals: NodeInternals, elevateEdgesOnSelect: boolean = false) {
|
||||
let maxLevel = -1;
|
||||
|
||||
const levelLookup = edges.reduce<Record<string, Edge[]>>((tree, edge) => {
|
||||
const z = isNumeric(edge.zIndex)
|
||||
? edge.zIndex
|
||||
: Math.max(nodeInternals.get(edge.source)?.z || 0, nodeInternals.get(edge.target)?.z || 0);
|
||||
const hasZIndex = isNumeric(edge.zIndex);
|
||||
let z = hasZIndex ? edge.zIndex! : 0;
|
||||
|
||||
if (elevateEdgesOnSelect) {
|
||||
z = hasZIndex
|
||||
? edge.zIndex!
|
||||
: Math.max(nodeInternals.get(edge.source)?.z || 0, nodeInternals.get(edge.target)?.z || 0);
|
||||
}
|
||||
|
||||
if (tree[z]) {
|
||||
tree[z].push(edge);
|
||||
} else {
|
||||
@@ -42,7 +48,7 @@ function groupEdgesByZLevel(edges: Edge[], nodeInternals: NodeInternals) {
|
||||
return edgeTree;
|
||||
}
|
||||
|
||||
function useVisibleEdges(onlyRenderVisible: boolean, nodeInternals: NodeInternals) {
|
||||
function useVisibleEdges(onlyRenderVisible: boolean, nodeInternals: NodeInternals, elevateEdgesOnSelect: boolean) {
|
||||
const edges = useStore(
|
||||
useCallback(
|
||||
(s: ReactFlowState) => {
|
||||
@@ -77,7 +83,7 @@ function useVisibleEdges(onlyRenderVisible: boolean, nodeInternals: NodeInternal
|
||||
)
|
||||
);
|
||||
|
||||
return groupEdgesByZLevel(edges, nodeInternals);
|
||||
return groupEdgesByZLevel(edges, nodeInternals, elevateEdgesOnSelect);
|
||||
}
|
||||
|
||||
export default useVisibleEdges;
|
||||
|
||||
+8
-2
@@ -45,9 +45,12 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals)
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const z = isNumeric(node.zIndex) ? node.zIndex : node.dragging || node.selected ? 1000 : 0;
|
||||
const currInternals = nodeInternals.get(node.id);
|
||||
|
||||
const internals: Node = {
|
||||
...nodeInternals.get(node.id),
|
||||
width: currInternals?.width,
|
||||
height: currInternals?.height,
|
||||
handleBounds: currInternals?.handleBounds,
|
||||
...node,
|
||||
positionAbsolute: {
|
||||
x: node.position.x,
|
||||
@@ -148,7 +151,10 @@ export function createPositionChange({
|
||||
]
|
||||
: currentExtent;
|
||||
} else {
|
||||
console.warn('Only child nodes can use parent extent');
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[React Flow]: Only child nodes can use a parent extent. Help: https://reactflow.dev/error#500');
|
||||
}
|
||||
currentExtent = nodeExtent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import React, { CSSProperties, HTMLAttributes, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
|
||||
import React, {
|
||||
ButtonHTMLAttributes,
|
||||
CSSProperties,
|
||||
HTMLAttributes,
|
||||
MouseEvent as ReactMouseEvent,
|
||||
WheelEvent,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
OnSelectionChangeFunc,
|
||||
@@ -118,6 +124,7 @@ export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
|
||||
connectOnClick?: boolean;
|
||||
attributionPosition?: AttributionPosition;
|
||||
proOptions?: ProOptions;
|
||||
elevateEdgesOnSelect?: boolean;
|
||||
}
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
@@ -144,7 +151,7 @@ export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
|
||||
onInteractiveChange?: (interactiveStatus: boolean) => void;
|
||||
}
|
||||
|
||||
export interface ControlButtonProps extends HTMLAttributes<HTMLButtonElement> {}
|
||||
export interface ControlButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
export interface BackgroundProps extends HTMLAttributes<SVGElement> {
|
||||
variant?: BackgroundVariant;
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
import React, { CSSProperties, HTMLAttributes, ReactNode } from 'react';
|
||||
import React, { CSSProperties, ComponentType, HTMLAttributes, ReactNode } from 'react';
|
||||
import { Connection } from './general';
|
||||
import { HandleElement, HandleType } from './handles';
|
||||
import { Node } from './nodes';
|
||||
@@ -101,8 +101,8 @@ export interface WrapEdgeProps<T = any> {
|
||||
style?: CSSProperties;
|
||||
source: string;
|
||||
target: string;
|
||||
sourceHandleId: string | null;
|
||||
targetHandleId: string | null;
|
||||
sourceHandleId?: string | null;
|
||||
targetHandleId?: string | null;
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
@@ -111,7 +111,7 @@ export interface WrapEdgeProps<T = any> {
|
||||
targetPosition: Position;
|
||||
elementsSelectable?: boolean;
|
||||
hidden?: boolean;
|
||||
onEdgeUpdate: OnEdgeUpdateFunc;
|
||||
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||
onContextMenu?: EdgeMouseHandler;
|
||||
onMouseEnter?: EdgeMouseHandler;
|
||||
onMouseMove?: EdgeMouseHandler;
|
||||
@@ -162,7 +162,7 @@ export type ConnectionLineComponentProps = {
|
||||
sourceHandle?: HandleElement;
|
||||
};
|
||||
|
||||
export type ConnectionLineComponent = React.ComponentType<ConnectionLineComponentProps>;
|
||||
export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps>;
|
||||
|
||||
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void;
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||
import { MouseEvent as ReactMouseEvent, ComponentType, MemoExoticComponent } 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 { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate, NodeProps, WrapNodeProps } from './nodes';
|
||||
import { Edge, EdgeProps, WrapEdgeProps } 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 NodeTypes = { [key: string]: ComponentType<NodeProps> };
|
||||
export type NodeTypesWrapped = { [key: string]: MemoExoticComponent<ComponentType<WrapNodeProps>> };
|
||||
export type EdgeTypes = { [key: string]: ComponentType<EdgeProps> };
|
||||
export type EdgeTypesWrapped = { [key: string]: MemoExoticComponent<ComponentType<WrapEdgeProps>> };
|
||||
|
||||
export type FitView = (fitViewOptions?: FitViewOptions) => void;
|
||||
|
||||
|
||||
+18
-3
@@ -57,7 +57,12 @@ const connectionExists = (edge: Edge, edges: Edge[]) => {
|
||||
|
||||
export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => {
|
||||
if (!edgeParams.source || !edgeParams.target) {
|
||||
console.warn("Can't create edge. An edge needs a source and a target.");
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
"[React Flow]: Can't create edge. An edge needs a source and a target. Help: https://reactflow.dev/error#600"
|
||||
);
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
@@ -80,14 +85,24 @@ export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] =>
|
||||
|
||||
export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[]): Edge[] => {
|
||||
if (!newConnection.source || !newConnection.target) {
|
||||
console.warn("Can't create new edge. An edge needs a source and a target.");
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
"[React Flow]: Can't create a new edge. An edge needs a source and a target. Help: https://reactflow.dev/error#600"
|
||||
);
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
const foundEdge = edges.find((e) => e.id === oldEdge.id) as Edge;
|
||||
|
||||
if (!foundEdge) {
|
||||
console.warn(`The old edge with id=${oldEdge.id} does not exist.`);
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
`[React Flow]: The old edge with id=${oldEdge.id} does not exist. Help: https://reactflow.dev/error#700`
|
||||
);
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user