@@ -542,6 +542,7 @@ You can find all examples in the [example](example) folder or check out the live
|
|||||||
- [interaction](https://reactflow.dev/interaction)
|
- [interaction](https://reactflow.dev/interaction)
|
||||||
- [provider](https://reactflow.dev/provider)
|
- [provider](https://reactflow.dev/provider)
|
||||||
- [hidden](https://reactflow.dev/hidden)
|
- [hidden](https://reactflow.dev/hidden)
|
||||||
|
- [edge types](https://reactflow.dev/edge-types)
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
describe('Basic Flow Rendering', () => {
|
describe('Basic Flow Rendering', () => {
|
||||||
it('renders a flow with three nodes', () => {
|
before(() => {
|
||||||
cy.visit('/basic');
|
cy.visit('/basic');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a flow with three nodes', () => {
|
||||||
cy.get('.react-flow__renderer');
|
cy.get('.react-flow__renderer');
|
||||||
cy.get('.react-flow-basic-example'); // check if className prop works
|
cy.get('.react-flow-basic-example'); // check if className prop works
|
||||||
cy.get('.react-flow__node').should('have.length', 4);
|
cy.get('.react-flow__node').should('have.length', 4);
|
||||||
@@ -115,16 +117,10 @@ describe('Basic Flow Rendering', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('drags the pane', () => {
|
it('drags the pane', () => {
|
||||||
// for d3 we have to pass the window to the event
|
|
||||||
// https://github.com/cypress-io/cypress/issues/3441
|
|
||||||
|
|
||||||
const newPosition = {
|
|
||||||
clientX: Cypress.config('viewportWidth') * 0.6,
|
|
||||||
clientY: Cypress.config('viewportHeight') * 0.7,
|
|
||||||
};
|
|
||||||
|
|
||||||
const styleBeforeDrag = Cypress.$('.react-flow__nodes').css('transform');
|
const styleBeforeDrag = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
// for d3 we have to pass the window to the event
|
||||||
|
// https://github.com/cypress-io/cypress/issues/3441
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
cy.get('.react-flow__zoompane')
|
cy.get('.react-flow__zoompane')
|
||||||
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
describe('Controls Testing', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the control panel', () => {
|
||||||
|
cy.get('.react-flow__controls');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zooms in', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__controls-zoomin')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zooms out', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__controls-zoomout')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// view is already fitted so we drag the pane to un-fit it
|
||||||
|
it('drags the pane', () => {
|
||||||
|
const styleBeforeDrag = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
// for d3 we have to pass the window to the event
|
||||||
|
// https://github.com/cypress-io/cypress/issues/3441
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||||
|
.trigger('mousemove', 'bottomLeft')
|
||||||
|
.trigger('mouseup', { force: true, view: win })
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterDrag = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fits view', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__controls-fitview')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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__node:first').should('not.have.class', 'selected');
|
||||||
|
|
||||||
|
cy.get('.react-flow__controls-interactive')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses interactive control - interactive', () => {
|
||||||
|
cy.get('.react-flow__controls-interactive')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
describe('Custom Node Flow Rendering', () => {
|
|
||||||
it('renders a flow', () => {
|
|
||||||
cy.visit('/custom-node');
|
|
||||||
|
|
||||||
cy.get('.react-flow__renderer');
|
|
||||||
|
|
||||||
cy.get('.react-flow__node').should('have.length', 4);
|
|
||||||
cy.get('.react-flow__edge').should('have.length', 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
describe('Empty Flow Rendering', () => {
|
describe('Empty Flow Rendering', () => {
|
||||||
it('renders an empty flow', () => {
|
before(() => {
|
||||||
cy.visit('/empty');
|
cy.visit('/empty');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders an empty flow', () => {
|
||||||
cy.get('.react-flow__renderer');
|
cy.get('.react-flow__renderer');
|
||||||
cy.get('.react-flow__node').should('not.exist');
|
cy.get('.react-flow__node').should('not.exist');
|
||||||
cy.get('.react-flow__edge').should('not.exist');
|
cy.get('.react-flow__edge').should('not.exist');
|
||||||
@@ -16,26 +18,6 @@ describe('Empty Flow Rendering', () => {
|
|||||||
.trigger('mouseup', 'bottomRight', { force: true });
|
.trigger('mouseup', 'bottomRight', { force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a control panel', () => {
|
|
||||||
cy.get('.react-flow__controls');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses zoom in control', () => {
|
|
||||||
cy.get('.react-flow__controls-zoomin').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses zoom out control', () => {
|
|
||||||
cy.get('.react-flow__controls-zoomout').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses fit view control', () => {
|
|
||||||
cy.get('.react-flow__controls-fitview').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses lock view control', () => {
|
|
||||||
cy.get('.react-flow__controls-interactive');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders an empty mini map', () => {
|
it('renders an empty mini map', () => {
|
||||||
cy.get('.react-flow__minimap');
|
cy.get('.react-flow__minimap');
|
||||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { isNode, isEdge, getOutgoers, removeElements, addEdge } from '../../../src/utils/graph.ts';
|
||||||
|
|
||||||
|
const nodes = [
|
||||||
|
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||||
|
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||||
|
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||||
|
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const edges = [
|
||||||
|
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||||
|
{ id: 'e1-3', source: '1', target: '3' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const elements = [...nodes, ...edges];
|
||||||
|
|
||||||
|
describe('Graph Utils Testing', () => {
|
||||||
|
it('tests isNode function', () => {
|
||||||
|
expect(isNode(nodes[0])).to.be.true;
|
||||||
|
expect(isNode(edges[0])).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tests isEdge function', () => {
|
||||||
|
expect(isEdge(edges[0])).to.be.true;
|
||||||
|
expect(isEdge(nodes[0])).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tests getOutgoers function', () => {
|
||||||
|
const outgoers = getOutgoers(nodes[0], elements);
|
||||||
|
expect(outgoers.length).to.be.equal(2);
|
||||||
|
|
||||||
|
const noOutgoers = getOutgoers(nodes[1], elements);
|
||||||
|
expect(noOutgoers.length).to.be.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tests addEdge function', () => {
|
||||||
|
it('adds edge', () => {
|
||||||
|
const newEdge = { source: '2', target: '3' };
|
||||||
|
const nextElements = addEdge(newEdge, elements);
|
||||||
|
|
||||||
|
expect(nextElements.length).to.be.equal(elements.length + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to add invalid edge', () => {
|
||||||
|
const newEdge = { nosource: '1', notarget: '3' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
addEdge(newEdge, elements);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message);
|
||||||
|
|
||||||
|
expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to add edge with id that does not exist', () => {
|
||||||
|
const notExistingId = 'does-not-exist';
|
||||||
|
const newEdge = { source: notExistingId, target: '3' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
addEdge(newEdge, elements);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message);
|
||||||
|
expect(e.message).to.be.equal(`Can't create edge. Node with id=${notExistingId} does not exist.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tests removeElements function', () => {
|
||||||
|
it('removes a node', () => {
|
||||||
|
const nextElements = removeElements([nodes[0]], elements);
|
||||||
|
|
||||||
|
const nextNodes = nextElements.filter((e) => isNode(e));
|
||||||
|
const nextEdges = nextElements.filter((e) => isEdge(e));
|
||||||
|
|
||||||
|
expect(nextNodes.length).to.be.equal(nodes.length - 1);
|
||||||
|
expect(nextEdges.length).to.be.equal(edges.length - 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes multiple nodes', () => {
|
||||||
|
const elementsToRemove = [nodes[0], nodes[1]];
|
||||||
|
const nextElements = removeElements(elementsToRemove, elements);
|
||||||
|
const nextNodes = nextElements.filter((e) => isNode(e));
|
||||||
|
const nextEdges = nextElements.filter((e) => isEdge(e));
|
||||||
|
|
||||||
|
expect(nextNodes.length).to.be.equal(nodes.length - 2);
|
||||||
|
expect(nextEdges.length).to.be.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes no node', () => {
|
||||||
|
const nextElementsNoRemove = removeElements([], elements);
|
||||||
|
expect(nextElementsNoRemove.length).to.be.equal(elements.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to removes node that does not exist', () => {
|
||||||
|
const nextElementsNoRemove = removeElements([{ id: 'id-that-does-not-exist' }], elements);
|
||||||
|
expect(nextElementsNoRemove.length).to.be.equal(elements.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes an edge', () => {
|
||||||
|
const nextElements = removeElements([edges[0]], elements);
|
||||||
|
|
||||||
|
const nextNodes = nextElements.filter((e) => isNode(e));
|
||||||
|
const nextEdges = nextElements.filter((e) => isEdge(e));
|
||||||
|
|
||||||
|
expect(nextNodes.length).to.be.equal(nodes.length);
|
||||||
|
expect(nextEdges.length).to.be.equal(edges.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes multiple edges', () => {
|
||||||
|
const nextElements = removeElements([edges[0], edges[1]], elements);
|
||||||
|
|
||||||
|
const nextNodes = nextElements.filter((e) => isNode(e));
|
||||||
|
const nextEdges = nextElements.filter((e) => isEdge(e));
|
||||||
|
|
||||||
|
expect(nextNodes.length).to.be.equal(nodes.length);
|
||||||
|
expect(nextEdges.length).to.be.equal(edges.length - 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes node and edge', () => {
|
||||||
|
const nextElements = removeElements([nodes[0], edges[0]], elements);
|
||||||
|
|
||||||
|
const nextNodes = nextElements.filter((e) => isNode(e));
|
||||||
|
const nextEdges = nextElements.filter((e) => isEdge(e));
|
||||||
|
|
||||||
|
expect(nextNodes.length).to.be.equal(nodes.length - 1);
|
||||||
|
expect(nextEdges.length).to.be.equal(edges.length - 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
describe('Hidden Flow Rendering', () => {
|
describe('Hidden Flow Rendering', () => {
|
||||||
it('renders the initial flow', () => {
|
before(() => {
|
||||||
cy.visit('/hidden');
|
cy.visit('/hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders initial flow', () => {
|
||||||
cy.get('.react-flow__renderer');
|
cy.get('.react-flow__renderer');
|
||||||
cy.get('.react-flow__node').should('have.length', 4);
|
cy.get('.react-flow__node').should('have.length', 4);
|
||||||
cy.get('.react-flow__edge').should('have.length', 3);
|
cy.get('.react-flow__edge').should('have.length', 3);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
describe('Interaction Flow Rendering', () => {
|
describe('Interaction Flow Rendering', () => {
|
||||||
it('renders initial flow', () => {
|
before(() => {
|
||||||
cy.visit('/interaction');
|
cy.visit('/interaction');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders initial flow', () => {
|
||||||
cy.get('.react-flow__renderer');
|
cy.get('.react-flow__renderer');
|
||||||
cy.get('.react-flow__node').should('have.length', 4);
|
cy.get('.react-flow__node').should('have.length', 4);
|
||||||
cy.get('.react-flow__edge').should('have.length', 2);
|
cy.get('.react-flow__edge').should('have.length', 2);
|
||||||
@@ -23,6 +25,38 @@ describe('Interaction Flow Rendering', () => {
|
|||||||
cy.get('body').type('{shift}', { release: true });
|
cy.get('body').type('{shift}', { release: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('tries to connect to nodes', () => {
|
||||||
|
cy.get('.react-flow__node')
|
||||||
|
.contains('Node 3')
|
||||||
|
.find('.react-flow__handle.source')
|
||||||
|
.then(($el) => {
|
||||||
|
const pointerEvents = $el.css('pointer-events');
|
||||||
|
expect(pointerEvents).to.equal('none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to zoom by scroll', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.dblclick()
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).to.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to zoom by double click', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).to.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('toggles draggable mode', () => {
|
it('toggles draggable mode', () => {
|
||||||
cy.get('.react-flow__draggable').click();
|
cy.get('.react-flow__draggable').click();
|
||||||
});
|
});
|
||||||
@@ -47,4 +81,53 @@ describe('Interaction Flow Rendering', () => {
|
|||||||
it('selects an edge by click', () => {
|
it('selects an edge by click', () => {
|
||||||
cy.get('.react-flow__edge:first').click().should('have.class', 'selected');
|
cy.get('.react-flow__edge:first').click().should('have.class', 'selected');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('toggles connectable mode', () => {
|
||||||
|
cy.get('.react-flow__connectable').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('connects two nodes', () => {
|
||||||
|
cy.get('.react-flow__node')
|
||||||
|
.contains('Node 3')
|
||||||
|
.find('.react-flow__handle.source')
|
||||||
|
.trigger('mousedown', { which: 1 });
|
||||||
|
|
||||||
|
cy.get('.react-flow__node')
|
||||||
|
.contains('Node 4')
|
||||||
|
.find('.react-flow__handle.target')
|
||||||
|
.trigger('mousemove')
|
||||||
|
.trigger('mouseup', { force: true });
|
||||||
|
|
||||||
|
cy.get('.react-flow__edge').should('have.length', 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles zoom on scroll', () => {
|
||||||
|
cy.get('.react-flow__zoomonscroll').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zooms by scroll', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.trigger('wheel', 'topLeft', { deltaY: 200 })
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles zoom on double click', () => {
|
||||||
|
cy.get('.react-flow__zoomondbl').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zooms by double click', () => {
|
||||||
|
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.dblclick()
|
||||||
|
.then(() => {
|
||||||
|
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
describe('Minimap Testing', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the mini map', () => {
|
||||||
|
cy.get('.react-flow__minimap');
|
||||||
|
cy.get('.react-flow__minimap-mask');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has same number of nodes as the pane', () => {
|
||||||
|
const paneNodes = Cypress.$('.react-flow__node').length;
|
||||||
|
const minimapNodes = Cypress.$('.react-flow__minimap-node').length;
|
||||||
|
|
||||||
|
expect(paneNodes).equal(minimapNodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes zoom level', () => {
|
||||||
|
const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||||
|
const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||||
|
.then(() => {
|
||||||
|
const viewBoxAfterZoom = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||||
|
const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
|
expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom);
|
||||||
|
expect(maskPathBeforeZoom).to.not.equal(maskPathAfterZoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes node position', () => {
|
||||||
|
const xPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('x');
|
||||||
|
const yPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('y');
|
||||||
|
const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
|
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el) => {
|
||||||
|
const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('x');
|
||||||
|
const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('y');
|
||||||
|
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
|
expect(xPosBeforeDrag).to.not.equal(xPosAfterDrag);
|
||||||
|
expect(yPosBeforeDrag).to.not.equal(yPosAfterDrag);
|
||||||
|
expect(maskPathBeforeDrag).to.not.equal(maskPathAfterDrag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes node positions via pane drag', () => {
|
||||||
|
const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||||
|
const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
|
// for d3 we have to pass the window to the event
|
||||||
|
// https://github.com/cypress-io/cypress/issues/3441
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.get('.react-flow__zoompane')
|
||||||
|
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||||
|
.trigger('mousemove', 'bottomLeft')
|
||||||
|
.trigger('mouseup', { force: true, view: win })
|
||||||
|
.then(() => {
|
||||||
|
const viewBoxAfterDrag = Cypress.$('.react-flow__minimap').attr('viewBox');
|
||||||
|
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||||
|
|
||||||
|
expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag);
|
||||||
|
expect(maskPathBeforeDrag).to.not.equal(maskPathAfterDrag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
describe('Overview Flow Rendering', () => {
|
|
||||||
it('renders a flow', () => {
|
|
||||||
cy.visit('/');
|
|
||||||
|
|
||||||
cy.get('.react-flow__renderer');
|
|
||||||
|
|
||||||
cy.get('.react-flow__node').should('have.length', 7);
|
|
||||||
cy.get('.react-flow__edge').should('have.length', 6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a grid', () => {
|
|
||||||
cy.get('.react-flow__background');
|
|
||||||
|
|
||||||
const gridStroke = Cypress.$('.react-flow__background path').attr('fill');
|
|
||||||
expect(gridStroke).to.equal('#888');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
describe('Stress Flow Rendering', () => {
|
|
||||||
it('renders initial flow', () => {
|
|
||||||
cy.visit('/stress');
|
|
||||||
|
|
||||||
cy.get('.react-flow__renderer');
|
|
||||||
|
|
||||||
cy.get('.react-flow__node').should('have.length', 100);
|
|
||||||
cy.get('.react-flow__edge').should('have.length', 99);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -70,7 +70,7 @@ const CustomNodeFlow = () => {
|
|||||||
nodeTypes={{
|
nodeTypes={{
|
||||||
selectorNode: ColorSelectorNode,
|
selectorNode: ColorSelectorNode,
|
||||||
}}
|
}}
|
||||||
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
|
connectionLineStyle={{ stroke: '#ddd' }}
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
snapGrid={[16, 16]}
|
snapGrid={[16, 16]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Example for checking the different edge types and source and target positions
|
||||||
|
*/
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import ReactFlow, { removeElements, addEdge, MiniMap, Controls, Background } from 'react-flow-renderer';
|
||||||
|
import { getElements } from './utils';
|
||||||
|
|
||||||
|
const onLoad = (reactFlowInstance) => {
|
||||||
|
reactFlowInstance.fitView();
|
||||||
|
console.log(reactFlowInstance.getElements());
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialElements = getElements();
|
||||||
|
|
||||||
|
const EdgeTypesFlow = () => {
|
||||||
|
const [elements, setElements] = useState(initialElements);
|
||||||
|
const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els));
|
||||||
|
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
elements={elements}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onElementsRemove={onElementsRemove}
|
||||||
|
onConnect={onConnect}
|
||||||
|
minZoom={0.2}
|
||||||
|
>
|
||||||
|
<MiniMap />
|
||||||
|
<Controls />
|
||||||
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EdgeTypesFlow;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
const nodeWidth = 80;
|
||||||
|
const nodeGapWidth = nodeWidth * 2;
|
||||||
|
const nodeStyle = { width: nodeWidth, fontSize: 11, color: 'white' };
|
||||||
|
|
||||||
|
const sourceTargetPositions = [
|
||||||
|
{ source: 'bottom', target: 'top' },
|
||||||
|
{ source: 'right', target: 'left' },
|
||||||
|
];
|
||||||
|
const nodeColors = [
|
||||||
|
['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'],
|
||||||
|
['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8'],
|
||||||
|
];
|
||||||
|
const edgeTypes = ['default', 'step', 'smoothstep', 'straight'];
|
||||||
|
const offsets = [
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: -nodeGapWidth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: nodeGapWidth,
|
||||||
|
y: -nodeGapWidth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: nodeGapWidth,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: nodeGapWidth,
|
||||||
|
y: nodeGapWidth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: nodeGapWidth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -nodeGapWidth,
|
||||||
|
y: nodeGapWidth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -nodeGapWidth,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -nodeGapWidth,
|
||||||
|
y: -nodeGapWidth,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
const getNodeId = () => (id++).toString();
|
||||||
|
|
||||||
|
export function getElements() {
|
||||||
|
const initialElements = [];
|
||||||
|
|
||||||
|
for (let sourceTargetIndex = 0; sourceTargetIndex < sourceTargetPositions.length; sourceTargetIndex++) {
|
||||||
|
const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex];
|
||||||
|
|
||||||
|
for (let edgeTypeIndex = 0; edgeTypeIndex < edgeTypes.length; edgeTypeIndex++) {
|
||||||
|
const currEdgeType = edgeTypes[edgeTypeIndex];
|
||||||
|
|
||||||
|
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
|
||||||
|
const currOffset = offsets[offsetIndex];
|
||||||
|
|
||||||
|
const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] };
|
||||||
|
const sourcePosition = {
|
||||||
|
x: offsetIndex * nodeWidth * 4,
|
||||||
|
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300,
|
||||||
|
};
|
||||||
|
const sourceId = getNodeId();
|
||||||
|
const sourceData = { label: `Source ${sourceId}` };
|
||||||
|
const sourceNode = {
|
||||||
|
id: sourceId,
|
||||||
|
style,
|
||||||
|
data: sourceData,
|
||||||
|
position: sourcePosition,
|
||||||
|
sourcePosition: currSourceTargetPos.source,
|
||||||
|
targetPosition: currSourceTargetPos.target,
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetId = getNodeId();
|
||||||
|
const targetData = { label: `Target ${targetId}` };
|
||||||
|
const targetPosition = {
|
||||||
|
x: sourcePosition.x + currOffset.x,
|
||||||
|
y: sourcePosition.y + currOffset.y,
|
||||||
|
};
|
||||||
|
const targetNode = {
|
||||||
|
id: targetId,
|
||||||
|
style,
|
||||||
|
data: targetData,
|
||||||
|
position: targetPosition,
|
||||||
|
sourcePosition: currSourceTargetPos.source,
|
||||||
|
targetPosition: currSourceTargetPos.target,
|
||||||
|
};
|
||||||
|
|
||||||
|
initialElements.push(sourceNode);
|
||||||
|
initialElements.push(targetNode);
|
||||||
|
|
||||||
|
initialElements.push({ id: `${sourceId}-${targetId}`, source: sourceId, target: targetId, type: currEdgeType });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialElements;
|
||||||
|
}
|
||||||
@@ -22,8 +22,8 @@ const InteractionFlow = () => {
|
|||||||
const [isSelectable, setIsSelectable] = useState(false);
|
const [isSelectable, setIsSelectable] = useState(false);
|
||||||
const [isDraggable, setIsDraggable] = useState(false);
|
const [isDraggable, setIsDraggable] = useState(false);
|
||||||
const [isConnectable, setIsConnectable] = useState(false);
|
const [isConnectable, setIsConnectable] = useState(false);
|
||||||
const [zoomOnScroll, setZoomOnScroll] = useState(true);
|
const [zoomOnScroll, setZoomOnScroll] = useState(false);
|
||||||
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(true);
|
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
|
|||||||
@@ -95,8 +95,10 @@ const initialElements = [
|
|||||||
source: '5',
|
source: '5',
|
||||||
target: '7',
|
target: '7',
|
||||||
type: 'step',
|
type: 'step',
|
||||||
|
style: { stroke: '#f6ab6c' },
|
||||||
label: 'a step edge',
|
label: 'a step edge',
|
||||||
labelStyle: { fill: 'red', fontWeight: 700 },
|
animated: true,
|
||||||
|
labelStyle: { fill: '#f6ab6c', fontWeight: 700 },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -126,17 +128,18 @@ const OverviewFlow = () => {
|
|||||||
onSelectionChange={onSelectionChange}
|
onSelectionChange={onSelectionChange}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
|
connectionLineStyle={{ stroke: '#ddd' }}
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
snapGrid={[16, 16]}
|
snapGrid={[16, 16]}
|
||||||
>
|
>
|
||||||
<MiniMap
|
<MiniMap
|
||||||
nodeColor={(n) => {
|
nodeColor={(n) => {
|
||||||
if (n.type === 'input') return 'blue';
|
if (n.style?.background) return n.style.background;
|
||||||
if (n.type === 'output') return 'green';
|
if (n.type === 'input') return '#9999ff';
|
||||||
if (n.type === 'default') return 'red';
|
if (n.type === 'output') return '#79c9b7';
|
||||||
|
if (n.type === 'default') return '#ff6060';
|
||||||
|
|
||||||
return '#FFCC00';
|
return '#eee';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Controls />
|
<Controls />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Validation from './Validation';
|
|||||||
import Horizontal from './Horizontal';
|
import Horizontal from './Horizontal';
|
||||||
import Provider from './Provider';
|
import Provider from './Provider';
|
||||||
import Hidden from './Hidden';
|
import Hidden from './Hidden';
|
||||||
|
import EdgeTypes from './EdgeTypes';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -69,6 +70,10 @@ const routes = [
|
|||||||
path: '/hidden',
|
path: '/hidden',
|
||||||
component: Hidden,
|
component: Hidden,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/edge-types',
|
||||||
|
component: EdgeTypes,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const navLinks = routes.filter((route) => route.label);
|
const navLinks = routes.filter((route) => route.label);
|
||||||
|
|||||||
+4
-2
@@ -16,10 +16,12 @@
|
|||||||
"start:examples": "npm run build && cd example && npm start",
|
"start:examples": "npm run build && cd example && npm start",
|
||||||
"dev:wait": "start-server-and-test start:examples http-get://localhost:3000",
|
"dev:wait": "start-server-and-test start:examples http-get://localhost:3000",
|
||||||
"build:example": "npm install && npm run build && cd example && npm install && npm run build",
|
"build:example": "npm install && npm run build && cd example && npm install && npm run build",
|
||||||
"cy:run": "cypress run",
|
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cypress": "npm run dev:wait cy:open",
|
"cypress": "npm run dev:wait cy:open",
|
||||||
"test": "npm run dev:wait cy:run",
|
"test:chrome": "cypress run --browser chrome",
|
||||||
|
"test:firefox": "cypress run --browser firefox",
|
||||||
|
"test:all": "npm run test:chrome && npm run test:firefox",
|
||||||
|
"test": "npm run dev:wait test:chrome",
|
||||||
"release": "release-it"
|
"release": "release-it"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const MiniMapNode = ({ node, color, borderRadius }: MiniMapNodeProps) => {
|
|||||||
height,
|
height,
|
||||||
} = node.__rf;
|
} = node.__rf;
|
||||||
const { background, backgroundColor } = node.style || {};
|
const { background, backgroundColor } = node.style || {};
|
||||||
const fill = (background || backgroundColor || color) as string;
|
const fill = (color || background || backgroundColor) as string;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<rect
|
<rect
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useEffect, useState, CSSProperties } from 'react';
|
|||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { getBezierPath } from '../Edges/BezierEdge';
|
import { getBezierPath } from '../Edges/BezierEdge';
|
||||||
import { getStepPath } from '../Edges/StepEdge';
|
|
||||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
||||||
import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType, HandleType } from '../../types';
|
import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType, HandleType } from '../../types';
|
||||||
|
|
||||||
@@ -80,12 +79,18 @@ export default ({
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
});
|
});
|
||||||
} else if (connectionLineType === ConnectionLineType.Step) {
|
} else if (connectionLineType === ConnectionLineType.Step) {
|
||||||
dAttr = getStepPath({
|
dAttr = getSmoothStepPath({
|
||||||
|
xOffset,
|
||||||
|
yOffset,
|
||||||
|
centerX,
|
||||||
centerY,
|
centerY,
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
|
sourcePosition: sourceHandle?.position,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
|
targetPosition,
|
||||||
|
borderRadius: 0,
|
||||||
});
|
});
|
||||||
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
|
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
|
||||||
dAttr = getSmoothStepPath({
|
dAttr = getSmoothStepPath({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { memo } from 'react';
|
|||||||
|
|
||||||
import EdgeText from './EdgeText';
|
import EdgeText from './EdgeText';
|
||||||
import { getMarkerEnd } from './utils';
|
import { getMarkerEnd } from './utils';
|
||||||
import { EdgeBezierProps, Position } from '../../types';
|
import { EdgeSmoothStepProps, Position } from '../../types';
|
||||||
|
|
||||||
// These are some helper methods for drawing the round corners
|
// These are some helper methods for drawing the round corners
|
||||||
// The name indicates the direction of the path. "bottomLeftCorner" goes
|
// The name indicates the direction of the path. "bottomLeftCorner" goes
|
||||||
@@ -44,6 +44,7 @@ interface GetSmoothStepPathParams {
|
|||||||
targetX: number;
|
targetX: number;
|
||||||
targetY: number;
|
targetY: number;
|
||||||
targetPosition?: Position;
|
targetPosition?: Position;
|
||||||
|
borderRadius?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSmoothStepPath({
|
export function getSmoothStepPath({
|
||||||
@@ -57,9 +58,10 @@ export function getSmoothStepPath({
|
|||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
targetPosition = Position.Top,
|
targetPosition = Position.Top,
|
||||||
|
borderRadius = 5,
|
||||||
}: GetSmoothStepPathParams): string {
|
}: GetSmoothStepPathParams): string {
|
||||||
const cornerWidth = Math.min(5, Math.abs(targetX - sourceX));
|
const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX));
|
||||||
const cornerHeight = Math.min(5, Math.abs(targetY - sourceY));
|
const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY));
|
||||||
const cornerSize = Math.min(cornerWidth, cornerHeight, xOffset, yOffset);
|
const cornerSize = Math.min(cornerWidth, cornerHeight, xOffset, yOffset);
|
||||||
|
|
||||||
const leftAndRight = [Position.Left, Position.Right];
|
const leftAndRight = [Position.Left, Position.Right];
|
||||||
@@ -141,7 +143,8 @@ export default memo(
|
|||||||
targetPosition = Position.Top,
|
targetPosition = Position.Top,
|
||||||
arrowHeadType,
|
arrowHeadType,
|
||||||
markerEndId,
|
markerEndId,
|
||||||
}: EdgeBezierProps) => {
|
borderRadius = 5,
|
||||||
|
}: EdgeSmoothStepProps) => {
|
||||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||||
|
|
||||||
@@ -159,6 +162,7 @@ export default memo(
|
|||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
|
borderRadius,
|
||||||
});
|
});
|
||||||
|
|
||||||
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
|
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
|
||||||
|
|||||||
@@ -1,61 +1,8 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import EdgeText from './EdgeText';
|
import { EdgeSmoothStepProps } from '../../types';
|
||||||
import { getMarkerEnd } from './utils';
|
import SmoothStepEdge from './SmoothStepEdge';
|
||||||
import { EdgeProps } from '../../types';
|
|
||||||
|
|
||||||
interface GetStepPathParams {
|
export default memo((props: EdgeSmoothStepProps) => {
|
||||||
centerY: number;
|
return <SmoothStepEdge {...props} borderRadius={0} />;
|
||||||
sourceX: number;
|
});
|
||||||
sourceY: number;
|
|
||||||
targetX: number;
|
|
||||||
targetY: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStepPath({ centerY, sourceX, sourceY, targetX, targetY }: GetStepPathParams): string {
|
|
||||||
return `M ${sourceX},${sourceY}L ${sourceX},${centerY}L ${targetX},${centerY}L ${targetX},${targetY}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(
|
|
||||||
({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
label,
|
|
||||||
labelStyle,
|
|
||||||
labelShowBg,
|
|
||||||
labelBgStyle,
|
|
||||||
style,
|
|
||||||
arrowHeadType,
|
|
||||||
markerEndId,
|
|
||||||
}: 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 path = getStepPath({ centerY, sourceX, sourceY, targetX, targetY });
|
|
||||||
|
|
||||||
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
|
|
||||||
|
|
||||||
const text = label ? (
|
|
||||||
<EdgeText
|
|
||||||
x={centerX}
|
|
||||||
y={centerY}
|
|
||||||
label={label}
|
|
||||||
labelStyle={labelStyle}
|
|
||||||
labelShowBg={labelShowBg}
|
|
||||||
labelBgStyle={labelBgStyle}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<path style={style} className="react-flow__edge-path" d={path} markerEnd={markerEnd} />
|
|
||||||
{text}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|||||||
+8
-8
@@ -63,7 +63,7 @@
|
|||||||
.react-flow__edge-path {
|
.react-flow__edge-path {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #bbb;
|
stroke: #bbb;
|
||||||
stroke-width: 2;
|
stroke-width: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__edge-text {
|
.react-flow__edge-text {
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
.react-flow__connection-path {
|
.react-flow__connection-path {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #ddd;
|
stroke: #ddd;
|
||||||
stroke-width: 2;
|
stroke-width: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__nodes {
|
.react-flow__nodes {
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
.react-flow__node-input,
|
.react-flow__node-input,
|
||||||
.react-flow__node-output {
|
.react-flow__node-output {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #222;
|
color: #222;
|
||||||
@@ -127,16 +127,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__node-default {
|
|
||||||
background: #ff6060;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow__node-input {
|
.react-flow__node-input {
|
||||||
background: #9999ff;
|
background: #9999ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-flow__node-default {
|
||||||
|
background: #ff6060;
|
||||||
|
}
|
||||||
|
|
||||||
.react-flow__node-output {
|
.react-flow__node-output {
|
||||||
background: #55dd99;
|
background: #79c9b7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__nodesselection {
|
.react-flow__nodesselection {
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ export interface EdgeBezierProps extends EdgeProps {
|
|||||||
targetPosition: Position;
|
targetPosition: Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EdgeSmoothStepProps extends EdgeBezierProps {
|
||||||
|
borderRadius?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NodeProps {
|
export interface NodeProps {
|
||||||
id: ElementId;
|
id: ElementId;
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
+8
-1
@@ -33,9 +33,16 @@ const getEdgeId = ({ source, target }: Connection): ElementId => `reactflow__edg
|
|||||||
|
|
||||||
export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => {
|
export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => {
|
||||||
if (!edgeParams.source || !edgeParams.target) {
|
if (!edgeParams.source || !edgeParams.target) {
|
||||||
throw new Error('Can not create edge. An edge needs a source and a target');
|
throw new Error("Can't create edge. An edge needs a source and a target.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure that there is node with the target and one with the source id
|
||||||
|
[edgeParams.source, edgeParams.target].forEach((id) => {
|
||||||
|
if (!elements.find((e) => isNode(e) && e.id === id)) {
|
||||||
|
throw new Error(`Can't create edge. Node with id=${id} does not exist.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isEdge(edgeParams)) {
|
if (isEdge(edgeParams)) {
|
||||||
return elements.concat({ ...edgeParams });
|
return elements.concat({ ...edgeParams });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user