chore: setup monorepo using preconstruct
This commit is contained in:
10
packages/core/.npmignore
Normal file
10
packages/core/.npmignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
.cache
|
||||
.vscode
|
||||
.dependabot
|
||||
node_modules
|
||||
examples
|
||||
cypress
|
||||
.env
|
||||
tsconfig.json
|
||||
src
|
||||
2
packages/core/.npmrc
Normal file
2
packages/core/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
registry = "https://registry.npmjs.com/"
|
||||
legacy-peer-deps = true
|
||||
7
packages/core/.postcssrc.json
Normal file
7
packages/core/.postcssrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
{
|
||||
"plugins": {
|
||||
"autoprefixer" : {},
|
||||
"postcss-nested": {}
|
||||
}
|
||||
}
|
||||
1
packages/core/.prettierignore
Normal file
1
packages/core/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
*.md
|
||||
5
packages/core/.prettierrc.json
Normal file
5
packages/core/.prettierrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
12
packages/core/.release-it.json
Normal file
12
packages/core/.release-it.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore: release v${version}"
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
},
|
||||
"hooks": {
|
||||
"after:bump": "npm run build",
|
||||
"after:release": "echo Successfully released ${name} v${version}."
|
||||
}
|
||||
}
|
||||
3
packages/core/assets/icons/fitview.svg
Normal file
3
packages/core/assets/icons/fitview.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
|
||||
<path d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
3
packages/core/assets/icons/lock.svg
Normal file
3
packages/core/assets/icons/lock.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
||||
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 530 B |
3
packages/core/assets/icons/minus.svg
Normal file
3
packages/core/assets/icons/minus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 5">
|
||||
<path d="M0 0h32v4.2H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 96 B |
3
packages/core/assets/icons/plus.svg
Normal file
3
packages/core/assets/icons/plus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path d="M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 152 B |
3
packages/core/assets/icons/unlock.svg
Normal file
3
packages/core/assets/icons/unlock.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
||||
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
7
packages/core/cypress.json
Normal file
7
packages/core/cypress.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"viewportWidth": 1280,
|
||||
"viewportHeight": 720,
|
||||
"video": false,
|
||||
"pluginsFile": false
|
||||
}
|
||||
5
packages/core/cypress/fixtures/example.json
Normal file
5
packages/core/cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
165
packages/core/cypress/integration/flow/basic.spec.js
Normal file
165
packages/core/cypress/integration/flow/basic.spec.js
Normal file
@@ -0,0 +1,165 @@
|
||||
describe('Basic Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/basic');
|
||||
});
|
||||
|
||||
it('renders a flow with three nodes', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow-basic-example'); // check if className prop works
|
||||
cy.get('.react-flow__node').should('have.length', 4);
|
||||
cy.get('.react-flow__edge').should('have.length', 2);
|
||||
cy.get('.react-flow__node').children('.react-flow__handle');
|
||||
});
|
||||
|
||||
it('renders a grid', () => {
|
||||
cy.get('.react-flow__background');
|
||||
});
|
||||
|
||||
it('selects two nodes by clicks', () => {
|
||||
cy.get('body').type('{cmd}', { release: false });
|
||||
cy.get('.react-flow__node:first')
|
||||
.click()
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__node:last')
|
||||
.click()
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__node:first')
|
||||
.should('have.class', 'selected');
|
||||
cy.get('body').type('{cmd}', { release: true });
|
||||
});
|
||||
|
||||
it('selects a node by click', () => {
|
||||
cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('deselects node', () => {
|
||||
cy.get('.react-flow__renderer').click('bottomLeft');
|
||||
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects an edge by click', () => {
|
||||
cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('deselects edge', () => {
|
||||
cy.get('.react-flow__renderer').click('bottomLeft');
|
||||
cy.get('.react-flow__edge:first').should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects one node with a selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 1000, 50, { which: 1, force: true })
|
||||
.trigger('mousemove', 1, 400, { which: 1 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 1, 200, { force: true });
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
cy.get('.react-flow__node').eq(1).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', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.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');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true });
|
||||
});
|
||||
|
||||
it('removes selection', () => {
|
||||
cy.get('.react-flow__renderer').click('bottomLeft');
|
||||
cy.get('.react-flow__nodesselection-rect').should('not.exist');
|
||||
});
|
||||
|
||||
it('selects an edge', () => {
|
||||
cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
// @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')
|
||||
.contains('Node 3')
|
||||
.find('.react-flow__handle.source')
|
||||
.trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.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', 3);
|
||||
});
|
||||
|
||||
// @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);
|
||||
// });
|
||||
|
||||
it('drags the pane', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__viewport').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__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');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('zooms the pane', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
81
packages/core/cypress/integration/flow/controls.spec.js
Normal file
81
packages/core/cypress/integration/flow/controls.spec.js
Normal file
@@ -0,0 +1,81 @@
|
||||
describe('Controls Testing', () => {
|
||||
before(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('renders the control panel', () => {
|
||||
cy.get('.react-flow__controls');
|
||||
});
|
||||
|
||||
it('zooms in', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__controls-zoomin')
|
||||
.click()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).to.not.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('zooms out', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__controls-zoomout')
|
||||
.click()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').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__viewport').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__renderer')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fits view', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__controls-fitview')
|
||||
.click()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').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__pane').click('topLeft');
|
||||
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({ force: true }).should('have.class', 'selected');
|
||||
});
|
||||
});
|
||||
});
|
||||
30
packages/core/cypress/integration/flow/draghandle.spec.js
Normal file
30
packages/core/cypress/integration/flow/draghandle.spec.js
Normal file
@@ -0,0 +1,30 @@
|
||||
describe('DragHandle Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/draghandle');
|
||||
});
|
||||
|
||||
it('renders a flow with a node', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('tries to drag a node', () => {
|
||||
const $nodeElement = Cypress.$('.react-flow__node:first');
|
||||
const styleBeforeDrag = $nodeElement.css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 500 }).then(() => {
|
||||
const styleAfterDrag = $nodeElement.css('transform');
|
||||
expect(styleBeforeDrag).to.be.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const $nodeElement = Cypress.$('.react-flow__node:first');
|
||||
const styleBeforeDrag = $nodeElement.css('transform');
|
||||
|
||||
cy.drag('.custom-drag-handle:first', { x: 500, y: 500 }).then(() => {
|
||||
const styleAfterDrag = $nodeElement.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
39
packages/core/cypress/integration/flow/empty.spec.js
Normal file
39
packages/core/cypress/integration/flow/empty.spec.js
Normal file
@@ -0,0 +1,39 @@
|
||||
describe('Empty Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/empty');
|
||||
});
|
||||
|
||||
it('renders an empty flow', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('not.exist');
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
});
|
||||
|
||||
it('renders empty selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, force: true })
|
||||
.trigger('mousemove', 'bottomLeft', { which: 1 })
|
||||
.trigger('mouseup', 'bottomLeft', { force: true });
|
||||
|
||||
cy.get('body').type('{shift}', { release: true });
|
||||
});
|
||||
|
||||
it('adds two nodes', () => {
|
||||
cy.contains('add node').click();
|
||||
cy.contains('add node').click();
|
||||
});
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.last()
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove')
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
71
packages/core/cypress/integration/flow/graph-utils.spec.js
Normal file
71
packages/core/cypress/integration/flow/graph-utils.spec.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { isNode, isEdge, getOutgoers, getIncomers, addEdge } from '../../../dist/esm/index.js';
|
||||
|
||||
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' },
|
||||
{ id: 'e2-3', source: '2', target: '3' },
|
||||
];
|
||||
|
||||
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], nodes, edges);
|
||||
|
||||
expect(outgoers.length).to.be.equal(2);
|
||||
|
||||
const noOutgoers = getOutgoers(nodes[2], nodes, edges);
|
||||
expect(noOutgoers.length).to.be.equal(0);
|
||||
});
|
||||
|
||||
it('tests getIncomers function', () => {
|
||||
const incomers = getIncomers(nodes[2], nodes, edges);
|
||||
expect(incomers.length).to.be.equal(2);
|
||||
|
||||
const noIncomers = getIncomers(nodes[0], nodes, edges);
|
||||
expect(noIncomers.length).to.be.equal(0);
|
||||
});
|
||||
|
||||
describe('tests addEdge function', () => {
|
||||
it('adds edge', () => {
|
||||
const newEdge = { source: '1', target: '4' };
|
||||
const nextEdges = addEdge(newEdge, edges);
|
||||
|
||||
expect(nextEdges.length).to.be.equal(edges.length + 1);
|
||||
});
|
||||
|
||||
it('tries to add existing edge', () => {
|
||||
const newEdge = { source: '2', target: '3' };
|
||||
const nextEdges = addEdge(newEdge, edges);
|
||||
|
||||
expect(nextEdges.length).to.be.equal(edges.length);
|
||||
});
|
||||
|
||||
it('tries to add invalid edge', () => {
|
||||
const newEdge = { nosource: '1', notarget: '3' };
|
||||
|
||||
try {
|
||||
addEdge(newEdge, edges);
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
|
||||
expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
32
packages/core/cypress/integration/flow/hidden.spec.js
Normal file
32
packages/core/cypress/integration/flow/hidden.spec.js
Normal file
@@ -0,0 +1,32 @@
|
||||
describe('Hidden Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/hidden');
|
||||
});
|
||||
|
||||
it('renders empty flow', () => {
|
||||
cy.get('.react-flow__node').should('not.exist');
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||
});
|
||||
|
||||
it('toggles isHidden mode', () => {
|
||||
cy.get('.react-flow__ishidden').click();
|
||||
});
|
||||
|
||||
it('renders initial flow', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('have.length', 4);
|
||||
cy.get('.react-flow__edge').should('have.length', 3);
|
||||
cy.get('.react-flow__minimap-node').should('have.length', 4);
|
||||
});
|
||||
|
||||
it('toggles isHidden mode again', () => {
|
||||
cy.get('.react-flow__ishidden').click();
|
||||
});
|
||||
|
||||
it('renders empty flow', () => {
|
||||
cy.get('.react-flow__node').should('not.exist');
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||
});
|
||||
});
|
||||
151
packages/core/cypress/integration/flow/interaction.spec.js
Normal file
151
packages/core/cypress/integration/flow/interaction.spec.js
Normal file
@@ -0,0 +1,151 @@
|
||||
describe('Interaction Flow Rendering', () => {
|
||||
before(() => {
|
||||
cy.visit('/interaction');
|
||||
});
|
||||
|
||||
it('renders initial flow', () => {
|
||||
cy.get('.react-flow__renderer');
|
||||
cy.get('.react-flow__node').should('have.length', 4);
|
||||
cy.get('.react-flow__edge').should('have.length', 2);
|
||||
cy.get('.react-flow__node').children('.react-flow__handle');
|
||||
});
|
||||
|
||||
it('tries to select a node by click', () => {
|
||||
const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
|
||||
expect(pointerEvents).to.equal('none');
|
||||
});
|
||||
|
||||
it('tries to select an edge by click', () => {
|
||||
const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
|
||||
expect(pointerEvents).to.equal('none');
|
||||
});
|
||||
|
||||
it('toggles on capture element click', () => {
|
||||
cy.get('.react-flow__captureelementclick').click();
|
||||
});
|
||||
|
||||
it('allows node clicks when enabled', () => {
|
||||
const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
|
||||
expect(pointerEvents).to.equal('all');
|
||||
});
|
||||
|
||||
it('allows edge clicks when enabled', () => {
|
||||
const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
|
||||
expect(pointerEvents.toLowerCase()).to.equal('visiblestroke');
|
||||
});
|
||||
|
||||
it('tries to do a selection', () => {
|
||||
cy.get('body').type('{shift}', { release: false }).get('.react-flow__selectionpane').should('not.exist');
|
||||
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 double click', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
|
||||
cy.get('.react-flow__renderer')
|
||||
.dblclick()
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
expect(styleBeforeZoom).to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('tries to zoom by scroll', () => {
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
|
||||
cy.get('.react-flow__renderer')
|
||||
.trigger('wheel', 'topLeft', { deltaY: -200 })
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform');
|
||||
expect(styleBeforeZoom).to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles draggable mode', () => {
|
||||
cy.get('.react-flow__draggable').click();
|
||||
});
|
||||
|
||||
it('drags a node', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 325, y: 100 }).then(($el) => {
|
||||
const styleAfterDrag = $el.css('transform');
|
||||
expect(styleBeforeDrag).to.not.equal(styleAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles selectable mode', () => {
|
||||
cy.get('.react-flow__selectable').click();
|
||||
});
|
||||
|
||||
it('selects a node by click', () => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects an edge by click', () => {
|
||||
cy.get('.react-flow__edge:first').click({ force: true });
|
||||
cy.get('.react-flow__edge:first').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', { button: 0 });
|
||||
|
||||
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__viewport').css('transform');
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles zoom on double click', () => {
|
||||
cy.get('.react-flow__zoomondbl').click();
|
||||
});
|
||||
|
||||
it('zooms by double click', () => {
|
||||
cy.get('.react-flow__controls-zoomout').click();
|
||||
const styleBeforeZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
cy.get('.react-flow__pane')
|
||||
.dblclick()
|
||||
.wait(50)
|
||||
.then(() => {
|
||||
const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform');
|
||||
expect(styleBeforeZoom).not.to.equal(styleAfterZoom);
|
||||
});
|
||||
});
|
||||
});
|
||||
72
packages/core/cypress/integration/flow/minimap.spec.js
Normal file
72
packages/core/cypress/integration/flow/minimap.spec.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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;
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
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__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');
|
||||
|
||||
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');
|
||||
|
||||
cy.drag('.react-flow__node:first', { x: 500, y: 25 })
|
||||
.wait(100)
|
||||
.then(() => {
|
||||
const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('x');
|
||||
const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('y');
|
||||
|
||||
expect(xPosBeforeDrag).to.not.equal(xPosAfterDrag);
|
||||
expect(yPosBeforeDrag).to.not.equal(yPosAfterDrag);
|
||||
});
|
||||
});
|
||||
|
||||
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__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');
|
||||
const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d');
|
||||
|
||||
expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag);
|
||||
expect(maskPathBeforeDrag).to.not.equal(maskPathAfterDrag);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
38
packages/core/cypress/support/commands.js
Normal file
38
packages/core/cypress/support/commands.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('drag', (selector, { x, y }) => {
|
||||
return cy
|
||||
.window()
|
||||
.then((window) =>
|
||||
cy
|
||||
.get(selector)
|
||||
.trigger('mousedown', { which: 1, view: window })
|
||||
.trigger('mousemove', { clientX: x, clientY: y, force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { view: window, force: true })
|
||||
);
|
||||
});
|
||||
25
packages/core/cypress/support/index.js
Normal file
25
packages/core/cypress/support/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/;
|
||||
|
||||
Cypress.on('uncaught:exception', (err) => {
|
||||
if (resizeObserverLoopErrRe.test(err.message)) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
25
packages/core/example/.gitignore
vendored
Normal file
25
packages/core/example/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
/src_oldapi
|
||||
23
packages/core/example/README.md
Normal file
23
packages/core/example/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# React Flow Examples
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
Starts a dev server at http://localhost:3000
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
39417
packages/core/example/package-lock.json
generated
Normal file
39417
packages/core/example/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
packages/core/example/package.json
Normal file
43
packages/core/example/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "react-flow-examples",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"dagre": "^0.8.5",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "file:../node_modules/react",
|
||||
"react-dom": "file:../node_modules/react-dom",
|
||||
"react-flow-renderer": "file:../",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dagre": "^0.7.47",
|
||||
"@types/localforage": "0.0.34",
|
||||
"@types/react": "file:../node_modules/@types/react",
|
||||
"@types/react-dom": "file:../node_modules/@types/react-dom",
|
||||
"@types/react-router-dom": "^5.3.3"
|
||||
}
|
||||
}
|
||||
1
packages/core/example/public/_redirects
Normal file
1
packages/core/example/public/_redirects
Normal file
@@ -0,0 +1 @@
|
||||
/* /index.html 200
|
||||
12
packages/core/example/public/index.html
Normal file
12
packages/core/example/public/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>React Flow Examples</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
3
packages/core/example/public/robots.txt
Normal file
3
packages/core/example/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
95
packages/core/example/src/Basic/index.tsx
Normal file
95
packages/core/example/src/Basic/index.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { MouseEvent } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Node,
|
||||
Edge,
|
||||
useReactFlow,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const defaultEdgeOptions = { zIndex: 0 };
|
||||
|
||||
const BasicFlow = () => {
|
||||
const instance = useReactFlow();
|
||||
|
||||
const updatePos = () => {
|
||||
instance.setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
node.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
};
|
||||
|
||||
return node;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const logToObject = () => console.log(instance.toObject());
|
||||
const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 });
|
||||
|
||||
const toggleClassnames = () => {
|
||||
instance.setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
node.className = node.className === 'light' ? 'dark' : 'light';
|
||||
|
||||
return node;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
defaultNodes={initialNodes}
|
||||
defaultEdges={initialEdges}
|
||||
onNodeClick={onNodeClick}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
className="react-flow-basic-example"
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
selectNodesOnDrag={false}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
reset transform
|
||||
</button>
|
||||
<button onClick={updatePos} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
|
||||
toggle classnames
|
||||
</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<BasicFlow />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
98
packages/core/example/src/ControlledUncontrolled/index.tsx
Normal file
98
packages/core/example/src/ControlledUncontrolled/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowProvider,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const defaultNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const defaultEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const defaultEdgeOptions = {
|
||||
animated: true,
|
||||
};
|
||||
|
||||
// This is bad practise. You should either use a controlled or an uncontrolled component.
|
||||
// This is just an example for testing the API.
|
||||
const ControlledUncontrolled = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(defaultNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState(defaultEdges);
|
||||
const instance = useReactFlow();
|
||||
|
||||
const logToObject = () => console.log(instance.toObject());
|
||||
const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 });
|
||||
|
||||
const updateNodePositions = () => {
|
||||
instance.setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
node.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
};
|
||||
|
||||
return node;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const updateEdgeColors = () => {
|
||||
instance.setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
edge.style = {
|
||||
stroke: '#ff5050',
|
||||
};
|
||||
|
||||
return edge;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
defaultNodes={defaultNodes}
|
||||
defaultEdges={defaultEdges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
fitView
|
||||
>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
reset transform
|
||||
</button>
|
||||
<button onClick={updateNodePositions} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={updateEdgeColors} style={{ marginRight: 5 }}>
|
||||
red edges
|
||||
</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ControlledUncontrolled />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React, { FC } from 'react';
|
||||
import { ConnectionLineComponentProps } from 'react-flow-renderer';
|
||||
|
||||
const ConnectionLine: FC<ConnectionLineComponentProps> = ({ sourceX, sourceY, targetX, targetY }) => {
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#222"
|
||||
strokeWidth={1.5}
|
||||
className="animated"
|
||||
d={`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`}
|
||||
/>
|
||||
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionLine;
|
||||
36
packages/core/example/src/CustomConnectionLine/index.tsx
Normal file
36
packages/core/example/src/CustomConnectionLine/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
addEdge,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import ConnectionLine from './ConnectionLine';
|
||||
|
||||
const initialNodes: Node[] = [{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }];
|
||||
const initialEdges: Edge[] = [];
|
||||
|
||||
const ConnectionLineFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
connectionLineComponent={ConnectionLine}
|
||||
onConnect={onConnect}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionLineFlow;
|
||||
34
packages/core/example/src/CustomNode/ColorSelectorNode.tsx
Normal file
34
packages/core/example/src/CustomNode/ColorSelectorNode.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { memo, FC, CSSProperties } from 'react';
|
||||
|
||||
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
|
||||
|
||||
const targetHandleStyle: CSSProperties = { background: '#555' };
|
||||
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
|
||||
const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: 10, top: 'auto' };
|
||||
|
||||
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||
|
||||
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
|
||||
<div>
|
||||
Custom Color Picker Node: <strong>{data.color}</strong>
|
||||
</div>
|
||||
<input className="nodrag" type="color" onChange={data.onChange} defaultValue={data.color} />
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id="a"
|
||||
style={sourceHandleStyleA}
|
||||
isConnectable={isConnectable}
|
||||
onMouseDown={(e) => {
|
||||
console.log('You trigger mousedown event', e);
|
||||
}}
|
||||
/>
|
||||
<Handle type="source" position={Position.Right} id="b" style={sourceHandleStyleB} isConnectable={isConnectable} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ColorSelectorNode);
|
||||
142
packages/core/example/src/CustomNode/index.tsx
Normal file
142
packages/core/example/src/CustomNode/index.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { useState, useEffect, MouseEvent } from 'react';
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Node,
|
||||
ReactFlowInstance,
|
||||
Position,
|
||||
SnapGrid,
|
||||
Connection,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import ColorSelectorNode from './ColorSelectorNode';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
||||
console.log('flow loaded:', reactFlowInstance);
|
||||
reactFlowInstance.fitView();
|
||||
};
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const initBgColor = '#1A192B';
|
||||
|
||||
const connectionLineStyle = { stroke: '#fff' };
|
||||
const snapGrid: SnapGrid = [16, 16];
|
||||
|
||||
const nodeTypes = {
|
||||
selectorNode: ColorSelectorNode,
|
||||
};
|
||||
|
||||
const CustomNodeFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const [bgColor, setBgColor] = useState<string>(initBgColor);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
if (node.id !== '2') {
|
||||
return node;
|
||||
}
|
||||
|
||||
const color = event.target.value;
|
||||
|
||||
setBgColor(color);
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
color,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
setNodes([
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'An input node' },
|
||||
position: { x: 0, y: 50 },
|
||||
sourcePosition: Position.Right,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'selectorNode',
|
||||
data: { onChange: onChange, color: initBgColor },
|
||||
style: { border: '1px solid #777', padding: 10 },
|
||||
position: { x: 250, y: 50 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'output',
|
||||
data: { label: 'Output A' },
|
||||
position: { x: 550, y: 25 },
|
||||
targetPosition: Position.Left,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'output',
|
||||
data: { label: 'Output B' },
|
||||
position: { x: 550, y: 100 },
|
||||
targetPosition: Position.Left,
|
||||
},
|
||||
]);
|
||||
|
||||
setEdges([
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
|
||||
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
|
||||
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const onConnect = (connection: Connection) =>
|
||||
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
style={{ background: bgColor }}
|
||||
onInit={onInit}
|
||||
nodeTypes={nodeTypes}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
snapToGrid={true}
|
||||
snapGrid={snapGrid}
|
||||
defaultZoom={1.5}
|
||||
fitView
|
||||
>
|
||||
<MiniMap
|
||||
nodeStrokeColor={(n: Node): string => {
|
||||
if (n.type === 'input') return '#0041d0';
|
||||
if (n.type === 'selectorNode') return bgColor;
|
||||
if (n.type === 'output') return '#ff0072';
|
||||
|
||||
return '#eee';
|
||||
}}
|
||||
nodeColor={(n: Node): string => {
|
||||
if (n.type === 'selectorNode') return bgColor;
|
||||
|
||||
return '#fff';
|
||||
}}
|
||||
/>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomNodeFlow;
|
||||
83
packages/core/example/src/DefaultNodes/index.tsx
Normal file
83
packages/core/example/src/DefaultNodes/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowProvider,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const defaultNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const defaultEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const defaultEdgeOptions = {
|
||||
animated: true,
|
||||
};
|
||||
|
||||
const DefaultNodes = () => {
|
||||
const instance = useReactFlow();
|
||||
|
||||
const logToObject = () => console.log(instance.toObject());
|
||||
const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 });
|
||||
|
||||
const updateNodePositions = () => {
|
||||
instance.setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
node.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
};
|
||||
|
||||
return node;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const updateEdgeColors = () => {
|
||||
instance.setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
edge.style = {
|
||||
stroke: '#ff5050',
|
||||
};
|
||||
|
||||
return edge;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} defaultEdgeOptions={defaultEdgeOptions} fitView>
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
reset transform
|
||||
</button>
|
||||
<button onClick={updateNodePositions} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={updateEdgeColors} style={{ marginRight: 5 }}>
|
||||
red edges
|
||||
</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<DefaultNodes />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
33
packages/core/example/src/DragHandle/DragHandleNode.tsx
Normal file
33
packages/core/example/src/DragHandle/DragHandleNode.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { memo, FC } from 'react';
|
||||
|
||||
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
|
||||
|
||||
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||
|
||||
const labelStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const dragHandleStyle = {
|
||||
display: 'inline-block',
|
||||
width: 25,
|
||||
height: 25,
|
||||
backgroundColor: 'teal',
|
||||
marginLeft: 5,
|
||||
borderRadius: '50%',
|
||||
};
|
||||
|
||||
const ColorSelectorNode: FC<NodeProps> = () => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Left} onConnect={onConnect} />
|
||||
<div style={labelStyle}>
|
||||
Only draggable here → <span className="custom-drag-handle" style={dragHandleStyle} />
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ColorSelectorNode);
|
||||
40
packages/core/example/src/DragHandle/index.tsx
Normal file
40
packages/core/example/src/DragHandle/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
||||
|
||||
import DragHandleNode from './DragHandleNode';
|
||||
|
||||
const nodeTypes = {
|
||||
dragHandleNode: DragHandleNode,
|
||||
};
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '2',
|
||||
type: 'dragHandleNode',
|
||||
dragHandle: '.custom-drag-handle',
|
||||
style: { border: '1px solid #ddd', padding: '20px 40px' },
|
||||
position: { x: 200, y: 200 },
|
||||
data: null,
|
||||
},
|
||||
];
|
||||
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const initialEdges: Edge[] = [];
|
||||
|
||||
const DragHandleFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges] = useEdgesState(initialEdges);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onNodesChange={onNodesChange}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
onNodeClick={onNodeClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DragHandleFlow;
|
||||
25
packages/core/example/src/DragNDrop/Sidebar.tsx
Normal file
25
packages/core/example/src/DragNDrop/Sidebar.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { DragEvent } from 'react';
|
||||
|
||||
const onDragStart = (event: DragEvent, nodeType: string) => {
|
||||
event.dataTransfer.setData('application/reactflow', nodeType);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
};
|
||||
|
||||
const Sidebar = () => {
|
||||
return (
|
||||
<aside>
|
||||
<div className="description">You can drag these nodes to the pane on the left.</div>
|
||||
<div className="react-flow__node-input" onDragStart={(event: DragEvent) => onDragStart(event, 'input')} draggable>
|
||||
Input Node
|
||||
</div>
|
||||
<div className="react-flow__node-default" onDragStart={(event: DragEvent) => onDragStart(event, 'default')} draggable>
|
||||
Default Node
|
||||
</div>
|
||||
<div className="react-flow__node-output" onDragStart={(event: DragEvent) => onDragStart(event, 'output')} draggable>
|
||||
Output Node
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
37
packages/core/example/src/DragNDrop/dnd.css
Normal file
37
packages/core/example/src/DragNDrop/dnd.css
Normal file
@@ -0,0 +1,37 @@
|
||||
.dndflow {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dndflow aside {
|
||||
border-right: 1px solid #eee;
|
||||
padding: 15px 10px;
|
||||
font-size: 12px;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
.dndflow aside > * {
|
||||
margin-bottom: 10px;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.dndflow aside .description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dndflow .reactflow-wrapper {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.dndflow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dndflow aside {
|
||||
width: 20%;
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
76
packages/core/example/src/DragNDrop/index.tsx
Normal file
76
packages/core/example/src/DragNDrop/index.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useState, DragEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
Controls,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import Sidebar from './Sidebar';
|
||||
|
||||
import './dnd.css';
|
||||
|
||||
const initialNodes: Node[] = [{ id: '1', type: 'input', data: { label: 'input node' }, position: { x: 250, y: 5 } }];
|
||||
|
||||
const onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
let id = 0;
|
||||
const getId = () => `dndnode_${id++}`;
|
||||
|
||||
const DnDFlow = () => {
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance>();
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const onInit = (rfi: ReactFlowInstance) => setReactFlowInstance(rfi);
|
||||
|
||||
const onDrop = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (reactFlowInstance) {
|
||||
const type = event.dataTransfer.getData('application/reactflow');
|
||||
const position = reactFlowInstance.project({ x: event.clientX, y: event.clientY - 40 });
|
||||
const newNode: Node = {
|
||||
id: getId(),
|
||||
type,
|
||||
position,
|
||||
data: { label: `${type} node` },
|
||||
};
|
||||
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dndflow">
|
||||
<ReactFlowProvider>
|
||||
<div className="reactflow-wrapper">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodesChange={onNodesChange}
|
||||
onConnect={onConnect}
|
||||
onInit={onInit}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
<Sidebar />
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DnDFlow;
|
||||
55
packages/core/example/src/EdgeTypes/index.tsx
Normal file
55
packages/core/example/src/EdgeTypes/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Example for checking the different edge types and source and target positions
|
||||
*/
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
import { getElements } from './utils';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
||||
reactFlowInstance.fitView();
|
||||
console.log(reactFlowInstance.getNodes());
|
||||
};
|
||||
|
||||
const { nodes: initialNodes, edges: initialEdges } = getElements();
|
||||
|
||||
const multiSelectionKeyCode = ['ShiftLeft', 'ShiftRight'];
|
||||
const deleteKeyCode = ['AltLeft+KeyD', 'Backspace'];
|
||||
|
||||
const EdgeTypesFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onInit={onInit}
|
||||
onConnect={onConnect}
|
||||
minZoom={0.2}
|
||||
zoomOnScroll={false}
|
||||
selectionKeyCode="a+s"
|
||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||
deleteKeyCode={deleteKeyCode}
|
||||
zoomActivationKeyCode="z"
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default EdgeTypesFlow;
|
||||
111
packages/core/example/src/EdgeTypes/utils.ts
Normal file
111
packages/core/example/src/EdgeTypes/utils.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Edge, Node, Position } from 'react-flow-renderer';
|
||||
|
||||
const nodeWidth = 80;
|
||||
const nodeGapWidth = nodeWidth * 2;
|
||||
const nodeStyle = { width: nodeWidth, fontSize: 11, color: 'white' };
|
||||
|
||||
const sourceTargetPositions = [
|
||||
{ source: Position.Bottom, target: Position.Top },
|
||||
{ source: Position.Right, target: Position.Left },
|
||||
];
|
||||
const nodeColors = [
|
||||
['#0c5956', '#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'],
|
||||
['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8', '#4fa6e0'],
|
||||
];
|
||||
const edgeTypes = ['default', 'step', 'smoothstep', 'straight', 'simplebezier'];
|
||||
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 = (): string => (id++).toString();
|
||||
|
||||
export function getElements(): { nodes: Node[]; edges: Edge[] } {
|
||||
const initialElements = { nodes: [] as Node[], edges: [] as Edge[] };
|
||||
|
||||
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: Node = {
|
||||
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: Node = {
|
||||
id: targetId,
|
||||
style,
|
||||
data: targetData,
|
||||
position: targetPosition,
|
||||
sourcePosition: currSourceTargetPos.source,
|
||||
targetPosition: currSourceTargetPos.target,
|
||||
};
|
||||
|
||||
initialElements.nodes.push(sourceNode);
|
||||
initialElements.nodes.push(targetNode);
|
||||
|
||||
initialElements.edges.push({
|
||||
id: `${sourceId}-${targetId}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: currEdgeType,
|
||||
} as Edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialElements;
|
||||
}
|
||||
28
packages/core/example/src/Edges/CustomEdge.tsx
Normal file
28
packages/core/example/src/Edges/CustomEdge.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { FC } from 'react';
|
||||
import { EdgeProps, getBezierPath } from 'react-flow-renderer';
|
||||
|
||||
const CustomEdge: FC<EdgeProps> = ({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
data,
|
||||
}) => {
|
||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
||||
|
||||
return (
|
||||
<>
|
||||
<path id={id} className="react-flow__edge-path" d={edgePath} />
|
||||
<text>
|
||||
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
|
||||
{data.text}
|
||||
</textPath>
|
||||
</text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomEdge;
|
||||
41
packages/core/example/src/Edges/CustomEdge2.tsx
Normal file
41
packages/core/example/src/Edges/CustomEdge2.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { FC } from 'react';
|
||||
import { EdgeProps, getBezierPath, EdgeText, getBezierEdgeCenter } from 'react-flow-renderer';
|
||||
|
||||
const CustomEdge: FC<EdgeProps> = ({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
data,
|
||||
}) => {
|
||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
||||
const [centerX, centerY] = getBezierEdgeCenter({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<path id={id} className="react-flow__edge-path" d={edgePath} />
|
||||
<EdgeText
|
||||
x={centerX}
|
||||
y={centerY}
|
||||
label={data.text}
|
||||
labelStyle={{ fill: 'white' }}
|
||||
labelShowBg
|
||||
labelBgStyle={{ fill: 'red' }}
|
||||
labelBgPadding={[2, 4]}
|
||||
labelBgBorderRadius={2}
|
||||
onClick={() => console.log(data)}
|
||||
/>
|
||||
;
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomEdge;
|
||||
151
packages/core/example/src/Edges/index.tsx
Normal file
151
packages/core/example/src/Edges/index.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
Connection,
|
||||
Controls,
|
||||
Edge,
|
||||
EdgeTypes,
|
||||
MarkerType,
|
||||
MiniMap,
|
||||
Node,
|
||||
ReactFlowInstance,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
} from 'react-flow-renderer';
|
||||
import CustomEdge from './CustomEdge';
|
||||
import CustomEdge2 from './CustomEdge2';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||
const onEdgeDoubleClick = (_: MouseEvent, edge: Edge) => console.log('dblclick', edge);
|
||||
const onEdgeMouseEnter = (_: MouseEvent, edge: Edge) => console.log('enter', edge);
|
||||
const onEdgeMouseMove = (_: MouseEvent, edge: Edge) => console.log('move', edge);
|
||||
const onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('leave', edge);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Input 1' }, position: { x: 250, y: 0 } },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
|
||||
{ id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } },
|
||||
{ id: '2b', data: { label: 'Node 2b' }, position: { x: -40, y: 300 } },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 250, y: 200 } },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } },
|
||||
{ id: '3a', data: { label: 'Node 3a' }, position: { x: 150, y: 300 } },
|
||||
{ id: '5', data: { label: 'Node 5' }, position: { x: 250, y: 400 } },
|
||||
{ id: '6', type: 'output', data: { label: 'Output 6' }, position: { x: 50, y: 550 } },
|
||||
{ id: '7', type: 'output', data: { label: 'Output 7' }, position: { x: 250, y: 550 } },
|
||||
{ id: '8', type: 'output', data: { label: 'Output 8' }, position: { x: 525, y: 600 } },
|
||||
{ id: '9', type: 'output', data: { label: 'Output 9' }, position: { x: 675, y: 500 } },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', className: 'normal-edge' },
|
||||
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
|
||||
{
|
||||
id: 'e2a-2b',
|
||||
source: '2a',
|
||||
target: '2b',
|
||||
type: 'simplebezier',
|
||||
label: 'simple bezier edge',
|
||||
},
|
||||
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
|
||||
{ id: 'e3-4', source: '3', target: '4', type: 'straight', label: 'straight edge' },
|
||||
{ id: 'e3-3a', source: '3', target: '3a', type: 'straight', label: 'label only edge', style: { stroke: 'none' } },
|
||||
{ id: 'e3-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } },
|
||||
{
|
||||
id: 'e5-7',
|
||||
source: '5',
|
||||
target: '7',
|
||||
label: 'label with styled bg',
|
||||
labelBgPadding: [8, 4],
|
||||
labelBgBorderRadius: 4,
|
||||
labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 },
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e5-8',
|
||||
source: '5',
|
||||
target: '8',
|
||||
type: 'custom',
|
||||
data: { text: 'custom edge' },
|
||||
},
|
||||
{
|
||||
id: 'e5-9',
|
||||
source: '5',
|
||||
target: '9',
|
||||
type: 'custom2',
|
||||
data: { text: 'custom edge 2' },
|
||||
},
|
||||
{
|
||||
id: 'e5-6',
|
||||
source: '5',
|
||||
target: '6',
|
||||
label: (
|
||||
<>
|
||||
<tspan>i am using</tspan>
|
||||
<tspan dy={10} x={0}>
|
||||
{'<tspan>'}
|
||||
</tspan>
|
||||
</>
|
||||
),
|
||||
labelStyle: { fill: 'red', fontWeight: 700 },
|
||||
style: { stroke: '#ffcc00' },
|
||||
markerEnd: {
|
||||
type: MarkerType.Arrow,
|
||||
color: '#FFCC00',
|
||||
markerUnits: 'userSpaceOnUse',
|
||||
width: 20,
|
||||
height: 20,
|
||||
strokeWidth: 2,
|
||||
},
|
||||
markerStart: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
color: '#FFCC00',
|
||||
orient: 'auto-start-reverse',
|
||||
markerUnits: 'userSpaceOnUse',
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const edgeTypes: EdgeTypes = {
|
||||
custom: CustomEdge,
|
||||
custom2: CustomEdge2,
|
||||
};
|
||||
|
||||
const EdgesFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onInit={onInit}
|
||||
snapToGrid={true}
|
||||
edgeTypes={edgeTypes}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||
onEdgeMouseMove={onEdgeMouseMove}
|
||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default EdgesFlow;
|
||||
59
packages/core/example/src/Empty/index.tsx
Normal file
59
packages/core/example/src/Empty/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { MouseEvent, CSSProperties } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Controls,
|
||||
Background,
|
||||
Node,
|
||||
BackgroundVariant,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
ReactFlowInstance,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
|
||||
const buttonStyle: CSSProperties = { position: 'absolute', left: 10, top: 10, zIndex: 4 };
|
||||
|
||||
const EmptyFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const onConnect = (params: Connection | Edge) => setEdges((els) => addEdge(params, els));
|
||||
const addRandomNode = () => {
|
||||
const nodeId = (nodes.length + 1).toString();
|
||||
const newNode: Node = {
|
||||
id: nodeId,
|
||||
data: { label: `Node: ${nodeId}` },
|
||||
position: { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight },
|
||||
};
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onInit={onInit}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={(p) => onConnect(p)}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onlyRenderVisibleElements={false}
|
||||
>
|
||||
<Controls />
|
||||
<Background variant={BackgroundVariant.Lines} />
|
||||
|
||||
<button type="button" onClick={addRandomNode} style={buttonStyle}>
|
||||
add node
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyFlow;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { FC } from 'react';
|
||||
import { getBezierPath, ConnectionLineComponentProps, Node } from 'react-flow-renderer';
|
||||
|
||||
import { getEdgeParams } from './utils';
|
||||
|
||||
const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
sourceNode,
|
||||
}) => {
|
||||
if (!sourceNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetNode = {
|
||||
id: 'connection-target',
|
||||
width: 1,
|
||||
height: 1,
|
||||
position: { x: targetX, y: targetY },
|
||||
} as Node;
|
||||
|
||||
const { sx, sy } = getEdgeParams(sourceNode, targetNode);
|
||||
const d = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<g>
|
||||
<path fill="none" stroke="#222" strokeWidth={1.5} className="animated" d={d} />
|
||||
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingConnectionLine;
|
||||
36
packages/core/example/src/FloatingEdges/FloatingEdge.tsx
Normal file
36
packages/core/example/src/FloatingEdges/FloatingEdge.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { FC, useMemo, CSSProperties } from 'react';
|
||||
import { EdgeProps, useStore, getBezierPath, ReactFlowState } from 'react-flow-renderer';
|
||||
|
||||
import { getEdgeParams } from './utils';
|
||||
|
||||
const nodeSelector = (s: ReactFlowState) => s.nodeInternals;
|
||||
|
||||
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, style }) => {
|
||||
const nodeInternals = useStore(nodeSelector);
|
||||
|
||||
const sourceNode = useMemo(() => nodeInternals.get(source), [source, nodeInternals]);
|
||||
const targetNode = useMemo(() => nodeInternals.get(target), [target, nodeInternals]);
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
|
||||
|
||||
const d = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
sourcePosition: sourcePos,
|
||||
targetPosition: targetPos,
|
||||
targetX: tx,
|
||||
targetY: ty,
|
||||
});
|
||||
|
||||
return (
|
||||
<g className="react-flow__connection">
|
||||
<path id={id} className="react-flow__edge-path" d={d} style={style as CSSProperties} />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingEdge;
|
||||
53
packages/core/example/src/FloatingEdges/index.tsx
Normal file
53
packages/core/example/src/FloatingEdges/index.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
ReactFlowInstance,
|
||||
EdgeTypes,
|
||||
Connection,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import './style.css';
|
||||
|
||||
import FloatingEdge from './FloatingEdge';
|
||||
import FloatingConnectionLine from './FloatingConnectionLine';
|
||||
import { createElements } from './utils';
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||
|
||||
const { nodes: initialNodes, edges: initialEdges } = createElements();
|
||||
|
||||
const edgeTypes: EdgeTypes = {
|
||||
floating: FloatingEdge,
|
||||
};
|
||||
|
||||
const FloatingEdges = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = useCallback((connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="floatingedges">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onInit={onInit}
|
||||
edgeTypes={edgeTypes}
|
||||
connectionLineComponent={FloatingConnectionLine}
|
||||
>
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingEdges;
|
||||
9
packages/core/example/src/FloatingEdges/style.css
Normal file
9
packages/core/example/src/FloatingEdges/style.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.floatingedges {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.floatingedges .react-flow__handle {
|
||||
opacity: 0;
|
||||
}
|
||||
106
packages/core/example/src/FloatingEdges/utils.ts
Normal file
106
packages/core/example/src/FloatingEdges/utils.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Position, XYPosition, Node, Edge } from 'react-flow-renderer';
|
||||
|
||||
// this helper function returns the intersection point
|
||||
// of the line between the center of the intersectionNode and the target node
|
||||
function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPosition {
|
||||
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
|
||||
|
||||
const {
|
||||
width: intersectionNodeWidth,
|
||||
height: intersectionNodeHeight,
|
||||
position: intersectionNodePosition,
|
||||
} = intersectionNode;
|
||||
const targetPosition = targetNode.position;
|
||||
|
||||
const w = (intersectionNodeWidth ?? 0) / 2;
|
||||
const h = (intersectionNodeHeight ?? 0) / 2;
|
||||
|
||||
const x2 = intersectionNodePosition.x + w;
|
||||
const y2 = intersectionNodePosition.y + h;
|
||||
const x1 = targetPosition.x + w;
|
||||
const y1 = targetPosition.y + h;
|
||||
|
||||
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
|
||||
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
|
||||
const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
|
||||
const xx3 = a * xx1;
|
||||
const yy3 = a * yy1;
|
||||
const x = w * (xx3 + yy3) + x2;
|
||||
const y = h * (-xx3 + yy3) + y2;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
// returns the position (top,right,bottom or right) passed node compared to the intersection point
|
||||
function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
|
||||
const n = { ...node.position, ...node };
|
||||
const nx = Math.round(n.x);
|
||||
const ny = Math.round(n.y);
|
||||
const px = Math.round(intersectionPoint.x);
|
||||
const py = Math.round(intersectionPoint.y);
|
||||
|
||||
if (px <= nx + 1) {
|
||||
return Position.Left;
|
||||
}
|
||||
if (px >= nx + (n.width ?? 0) - 1) {
|
||||
return Position.Right;
|
||||
}
|
||||
if (py <= ny + 1) {
|
||||
return Position.Top;
|
||||
}
|
||||
if (py >= n.y + (n.height ?? 0) - 1) {
|
||||
return Position.Bottom;
|
||||
}
|
||||
|
||||
return Position.Top;
|
||||
}
|
||||
|
||||
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
|
||||
export function getEdgeParams(source: Node, target: Node) {
|
||||
const sourceIntersectionPoint = getNodeIntersection(source, target);
|
||||
const targetIntersectionPoint = getNodeIntersection(target, source);
|
||||
|
||||
const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
|
||||
const targetPos = getEdgePosition(target, targetIntersectionPoint);
|
||||
|
||||
return {
|
||||
sx: sourceIntersectionPoint.x,
|
||||
sy: sourceIntersectionPoint.y,
|
||||
tx: targetIntersectionPoint.x,
|
||||
ty: targetIntersectionPoint.y,
|
||||
sourcePos,
|
||||
targetPos,
|
||||
};
|
||||
}
|
||||
|
||||
type NodesAndEdges = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
};
|
||||
|
||||
export function createElements(): NodesAndEdges {
|
||||
const nodes: Node[] = [];
|
||||
const edges: Edge[] = [];
|
||||
|
||||
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
|
||||
|
||||
nodes.push({ id: 'target', data: { label: 'Target' }, position: center });
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const degrees = i * (360 / 8);
|
||||
const radians = degrees * (Math.PI / 180);
|
||||
const x = 250 * Math.cos(radians) + center.x;
|
||||
const y = 250 * Math.sin(radians) + center.y;
|
||||
|
||||
nodes.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } });
|
||||
|
||||
edges.push({
|
||||
id: `edge-${i}`,
|
||||
target: 'target',
|
||||
source: `${i}`,
|
||||
type: 'floating',
|
||||
});
|
||||
}
|
||||
|
||||
return { nodes, edges };
|
||||
}
|
||||
83
packages/core/example/src/Hidden/index.tsx
Normal file
83
packages/core/example/src/Hidden/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', hidden: true, data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
|
||||
{ id: '2', hidden: true, data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||
{ id: '3', hidden: true, data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||
{ id: '4', hidden: true, data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e3-4', source: '3', target: '4' },
|
||||
];
|
||||
|
||||
const setHidden = (hidden: boolean) => (els: any[]) =>
|
||||
els.map((e: any) => {
|
||||
e.hidden = hidden;
|
||||
return e;
|
||||
});
|
||||
|
||||
const HiddenFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setNodes(setHidden(isHidden));
|
||||
setEdges(setHidden(isHidden));
|
||||
}, [isHidden, setEdges, setNodes]);
|
||||
|
||||
console.log(nodes);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onConnect={onConnect}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}>
|
||||
<div>
|
||||
<label htmlFor="ishidden">
|
||||
isHidden
|
||||
<input
|
||||
id="ishidden"
|
||||
type="checkbox"
|
||||
checked={isHidden}
|
||||
onChange={(event) => setIsHidden(event.target.checked)}
|
||||
className="react-flow__ishidden"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default HiddenFlow;
|
||||
235
packages/core/example/src/Interaction/index.tsx
Normal file
235
packages/core/example/src/Interaction/index.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import { useState, MouseEvent as ReactMouseEvent, WheelEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Node,
|
||||
Connection,
|
||||
Edge,
|
||||
PanOnScrollMode,
|
||||
Viewport,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ 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 initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const onNodeDragStart = (_: ReactMouseEvent, node: Node) => console.log('drag start', node);
|
||||
const onNodeDragStop = (_: ReactMouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: ReactMouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: ReactMouseEvent, edge: Edge) => console.log('click', edge);
|
||||
const onPaneClick = (event: ReactMouseEvent) => console.log('onPaneClick', event);
|
||||
const onPaneScroll = (event?: WheelEvent) => console.log('onPaneScroll', event);
|
||||
const onPaneContextMenu = (event: ReactMouseEvent) => console.log('onPaneContextMenu', event);
|
||||
const onMoveEnd = (_: TouchEvent | MouseEvent, viewport: Viewport) => console.log('onMoveEnd', viewport);
|
||||
|
||||
const InteractionFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((els) => addEdge(params, els));
|
||||
|
||||
const [isSelectable, setIsSelectable] = useState<boolean>(false);
|
||||
const [isDraggable, setIsDraggable] = useState<boolean>(false);
|
||||
const [isConnectable, setIsConnectable] = useState<boolean>(false);
|
||||
const [zoomOnScroll, setZoomOnScroll] = useState<boolean>(false);
|
||||
const [zoomOnPinch, setZoomOnPinch] = useState<boolean>(false);
|
||||
const [panOnScroll, setPanOnScroll] = useState<boolean>(false);
|
||||
const [panOnScrollMode, setPanOnScrollMode] = useState<PanOnScrollMode>(PanOnScrollMode.Free);
|
||||
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState<boolean>(false);
|
||||
const [panOnDrag, setPanOnDrag] = useState<boolean>(true);
|
||||
const [captureZoomClick, setCaptureZoomClick] = useState<boolean>(false);
|
||||
const [captureZoomScroll, setCaptureZoomScroll] = useState<boolean>(false);
|
||||
const [captureElementClick, setCaptureElementClick] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
elementsSelectable={isSelectable}
|
||||
nodesConnectable={isConnectable}
|
||||
nodesDraggable={isDraggable}
|
||||
zoomOnScroll={zoomOnScroll}
|
||||
zoomOnPinch={zoomOnPinch}
|
||||
panOnScroll={panOnScroll}
|
||||
panOnScrollMode={panOnScrollMode}
|
||||
zoomOnDoubleClick={zoomOnDoubleClick}
|
||||
onConnect={onConnect}
|
||||
onNodeClick={captureElementClick ? onNodeClick : undefined}
|
||||
onEdgeClick={captureElementClick ? onEdgeClick : undefined}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
panOnDrag={panOnDrag}
|
||||
onPaneClick={captureZoomClick ? onPaneClick : undefined}
|
||||
onPaneScroll={captureZoomScroll ? onPaneScroll : undefined}
|
||||
onPaneContextMenu={captureZoomClick ? onPaneContextMenu : undefined}
|
||||
onMoveEnd={onMoveEnd}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}>
|
||||
<div>
|
||||
<label htmlFor="draggable">
|
||||
nodesDraggable
|
||||
<input
|
||||
id="draggable"
|
||||
type="checkbox"
|
||||
checked={isDraggable}
|
||||
onChange={(event) => setIsDraggable(event.target.checked)}
|
||||
className="react-flow__draggable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="connectable">
|
||||
nodesConnectable
|
||||
<input
|
||||
id="connectable"
|
||||
type="checkbox"
|
||||
checked={isConnectable}
|
||||
onChange={(event) => setIsConnectable(event.target.checked)}
|
||||
className="react-flow__connectable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="selectable">
|
||||
elementsSelectable
|
||||
<input
|
||||
id="selectable"
|
||||
type="checkbox"
|
||||
checked={isSelectable}
|
||||
onChange={(event) => setIsSelectable(event.target.checked)}
|
||||
className="react-flow__selectable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="zoomonscroll">
|
||||
zoomOnScroll
|
||||
<input
|
||||
id="zoomonscroll"
|
||||
type="checkbox"
|
||||
checked={zoomOnScroll}
|
||||
onChange={(event) => setZoomOnScroll(event.target.checked)}
|
||||
className="react-flow__zoomonscroll"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="zoomonpinch">
|
||||
zoomOnPinch
|
||||
<input
|
||||
id="zoomonpinch"
|
||||
type="checkbox"
|
||||
checked={zoomOnPinch}
|
||||
onChange={(event) => setZoomOnPinch(event.target.checked)}
|
||||
className="react-flow__zoomonpinch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="panonscroll">
|
||||
panOnScroll
|
||||
<input
|
||||
id="panonscroll"
|
||||
type="checkbox"
|
||||
checked={panOnScroll}
|
||||
onChange={(event) => setPanOnScroll(event.target.checked)}
|
||||
className="react-flow__panonscroll"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="panonscrollmode">
|
||||
panOnScrollMode
|
||||
<select
|
||||
id="panonscrollmode"
|
||||
value={panOnScrollMode}
|
||||
onChange={(event) => setPanOnScrollMode(event.target.value as PanOnScrollMode)}
|
||||
className="react-flow__panonscrollmode"
|
||||
>
|
||||
<option value="free">free</option>
|
||||
<option value="horizontal">horizontal</option>
|
||||
<option value="vertical">vertical</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="zoomondbl">
|
||||
zoomOnDoubleClick
|
||||
<input
|
||||
id="zoomondbl"
|
||||
type="checkbox"
|
||||
checked={zoomOnDoubleClick}
|
||||
onChange={(event) => setZoomOnDoubleClick(event.target.checked)}
|
||||
className="react-flow__zoomondbl"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="panondrag">
|
||||
panOnDrag
|
||||
<input
|
||||
id="panondrag"
|
||||
type="checkbox"
|
||||
checked={panOnDrag}
|
||||
onChange={(event) => setPanOnDrag(event.target.checked)}
|
||||
className="react-flow__panondrag"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="capturezoompaneclick">
|
||||
capture onPaneClick
|
||||
<input
|
||||
id="capturezoompaneclick"
|
||||
type="checkbox"
|
||||
checked={captureZoomClick}
|
||||
onChange={(event) => setCaptureZoomClick(event.target.checked)}
|
||||
className="react-flow__capturezoompaneclick"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="capturezoompanescroll">
|
||||
capture onPaneScroll
|
||||
<input
|
||||
id="capturezoompanescroll"
|
||||
type="checkbox"
|
||||
checked={captureZoomScroll}
|
||||
onChange={(event) => setCaptureZoomScroll(event.target.checked)}
|
||||
className="react-flow__capturezoompanescroll"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="captureelementclick">
|
||||
capture onElementClick
|
||||
<input
|
||||
id="captureelementclick"
|
||||
type="checkbox"
|
||||
checked={captureElementClick}
|
||||
onChange={(event) => setCaptureElementClick(event.target.checked)}
|
||||
className="react-flow__captureelementclick"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default InteractionFlow;
|
||||
111
packages/core/example/src/Layouting/index.tsx
Normal file
111
packages/core/example/src/Layouting/index.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
Controls,
|
||||
Connection,
|
||||
CoordinateExtent,
|
||||
Position,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
MarkerType,
|
||||
EdgeMarker,
|
||||
} from 'react-flow-renderer';
|
||||
import dagre from 'dagre';
|
||||
|
||||
import initialItems from './initial-elements';
|
||||
|
||||
import './layouting.css';
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
const nodeExtent: CoordinateExtent = [
|
||||
[0, 0],
|
||||
[1000, 1000],
|
||||
];
|
||||
|
||||
const LayoutFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialItems.nodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialItems.edges);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onLayout = (direction: string) => {
|
||||
const isHorizontal = direction === 'LR';
|
||||
dagreGraph.setGraph({ rankdir: direction });
|
||||
|
||||
nodes.forEach((node) => {
|
||||
dagreGraph.setNode(node.id, { width: 150, height: 50 });
|
||||
});
|
||||
|
||||
edges.forEach((edge) => {
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
const layoutedNodes = nodes.map((node) => {
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
node.targetPosition = isHorizontal ? Position.Left : Position.Top;
|
||||
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||
// we need to pass a slightly different position in order to notify react flow about the change
|
||||
// @TODO how can we change the position handling so that we dont need this hack?
|
||||
node.position = { x: nodeWithPosition.x + Math.random() / 1000, y: nodeWithPosition.y };
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
setNodes(layoutedNodes);
|
||||
};
|
||||
|
||||
const unselect = () => {
|
||||
setNodes((nds) => nds.map((n) => ({ ...n, selected: false })));
|
||||
};
|
||||
|
||||
const changeMarker = () => {
|
||||
setEdges((eds) =>
|
||||
eds.map((e) => ({
|
||||
...e,
|
||||
markerEnd: {
|
||||
type: (e.markerEnd as EdgeMarker)?.type === MarkerType.Arrow ? MarkerType.ArrowClosed : MarkerType.Arrow,
|
||||
},
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="layoutflow">
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onConnect={onConnect}
|
||||
nodeExtent={nodeExtent}
|
||||
onInit={() => onLayout('TB')}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
<div className="controls">
|
||||
<button onClick={() => onLayout('TB')} style={{ marginRight: 10 }}>
|
||||
vertical layout
|
||||
</button>
|
||||
<button onClick={() => onLayout('LR')} style={{ marginRight: 10 }}>
|
||||
horizontal layout
|
||||
</button>
|
||||
<button onClick={() => unselect()}>unselect nodes</button>
|
||||
<button onClick={() => changeMarker()}>change marker</button>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LayoutFlow;
|
||||
76
packages/core/example/src/Layouting/initial-elements.ts
Normal file
76
packages/core/example/src/Layouting/initial-elements.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Node, Edge, XYPosition, MarkerType } from 'react-flow-renderer';
|
||||
|
||||
const position: XYPosition = { x: 0, y: 0 };
|
||||
|
||||
const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'input' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'node 2' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
data: { label: 'node 2a' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2b',
|
||||
data: { label: 'node 2b' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2c',
|
||||
data: { label: 'node 2c' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '2d',
|
||||
data: { label: 'node 2d' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'node 3' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'node 4' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: { label: 'node 5' },
|
||||
position,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'output',
|
||||
data: { label: 'output' },
|
||||
position,
|
||||
},
|
||||
{ id: '7', type: 'output', data: { label: 'output' }, position: { x: 400, y: 450 } },
|
||||
];
|
||||
|
||||
const edges: Edge[] = [
|
||||
{ id: 'e12', source: '1', target: '2', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e13', source: '1', target: '3', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e22a', source: '2', target: '2a', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e22b', source: '2', target: '2b', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e22c', source: '2', target: '2c', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
|
||||
{ id: 'e45', source: '4', target: '5', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e56', source: '5', target: '6', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e57', source: '5', target: '7', type: 'smoothstep', markerEnd: { type: MarkerType.Arrow } },
|
||||
];
|
||||
|
||||
const nodesAndEdges = { nodes, edges };
|
||||
|
||||
export default nodesAndEdges;
|
||||
11
packages/core/example/src/Layouting/layouting.css
Normal file
11
packages/core/example/src/Layouting/layouting.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.layoutflow {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layoutflow .controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
58
packages/core/example/src/MultiFlows/index.tsx
Normal file
58
packages/core/example/src/MultiFlows/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
Node,
|
||||
Edge,
|
||||
Connection,
|
||||
ReactFlowProvider,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
MarkerType,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import './multiflows.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true, markerEnd: { type: MarkerType.Arrow } },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const Flow: FC<{ id: string }> = ({ id }) => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = (params: Edge | Connection) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
id={id}
|
||||
>
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const MultiFlows: FC = () => (
|
||||
<div className="react-flow__example-multiflows">
|
||||
<Flow id="flow-a" />
|
||||
<Flow id="flow-b" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MultiFlows;
|
||||
13
packages/core/example/src/MultiFlows/multiflows.css
Normal file
13
packages/core/example/src/MultiFlows/multiflows.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.react-flow__example-multiflows {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.react-flow__example-multiflows .react-flow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.react-flow__example-multiflows .react-flow:first-child {
|
||||
border-right: 2px solid #333;
|
||||
}
|
||||
170
packages/core/example/src/NestedNodes/index.tsx
Normal file
170
packages/core/example/src/NestedNodes/index.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useState, MouseEvent, useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255, 0, 0, 0.8)', width: 200, height: 200 },
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
data: { label: 'Node 2a' },
|
||||
position: { x: 10, y: 50 },
|
||||
parentNode: '2',
|
||||
},
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 320, y: 100 }, className: 'light' },
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 320, y: 200 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255, 0, 0, 0.7)', width: 300, height: 300 },
|
||||
},
|
||||
{
|
||||
id: '4a',
|
||||
data: { label: 'Node 4a' },
|
||||
position: { x: 15, y: 65 },
|
||||
className: 'light',
|
||||
parentNode: '4',
|
||||
extent: 'parent',
|
||||
},
|
||||
{
|
||||
id: '4b',
|
||||
data: { label: 'Node 4b' },
|
||||
position: { x: 15, y: 120 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255, 0, 255, 0.7)', height: 150, width: 270 },
|
||||
parentNode: '4',
|
||||
},
|
||||
{
|
||||
id: '4b1',
|
||||
data: { label: 'Node 4b1' },
|
||||
position: { x: 20, y: 40 },
|
||||
className: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
{
|
||||
id: '4b2',
|
||||
data: { label: 'Node 4b2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e2a-4a', source: '2a', target: '4a' },
|
||||
{ id: 'e3-4', source: '3', target: '4' },
|
||||
{ id: 'e3-4b', source: '3', target: '4b' },
|
||||
{ id: 'e4a-4b1', source: '4a', target: '4b1' },
|
||||
{ id: 'e4a-4b2', source: '4a', target: '4b2' },
|
||||
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
||||
];
|
||||
|
||||
const NestedFlow = () => {
|
||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = useCallback((connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
}, []);
|
||||
const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||
|
||||
const updatePos = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
};
|
||||
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const logToObject = () => console.log(rfInstance?.toObject());
|
||||
const resetTransform = () => rfInstance?.setViewport({ x: 0, y: 0, zoom: 1 });
|
||||
|
||||
const toggleClassnames = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.className = n.className === 'light' ? 'dark' : 'light';
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const toggleChildNodes = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.hidden = !!n.parentNode && !n.hidden;
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onInit={onInit}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
className="react-flow-basic-example"
|
||||
defaultZoom={1.5}
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
onlyRenderVisibleElements={false}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
reset transform
|
||||
</button>
|
||||
<button onClick={updatePos} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
|
||||
toggle classnames
|
||||
</button>
|
||||
<button style={{ marginRight: 5 }} onClick={toggleChildNodes}>
|
||||
toggleChildNodes
|
||||
</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default NestedFlow;
|
||||
70
packages/core/example/src/NodeTypeChange/index.tsx
Normal file
70
packages/core/example/src/NodeTypeChange/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Node,
|
||||
Position,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
sourcePosition: Position.Right,
|
||||
type: 'input',
|
||||
data: { label: 'Input' },
|
||||
position: { x: 0, y: 80 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'output',
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
data: { label: 'A Node' },
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true }];
|
||||
|
||||
const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 };
|
||||
|
||||
const NodeTypeChangeFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const changeType = () => {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
if (node.type === 'input') {
|
||||
return node;
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
type: node.type === 'default' ? 'output' : 'default',
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
>
|
||||
<button onClick={changeType} style={buttonStyle}>
|
||||
change type
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeTypeChangeFlow;
|
||||
81
packages/core/example/src/NodeTypesObjectChange/index.tsx
Normal file
81
packages/core/example/src/NodeTypesObjectChange/index.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useState, CSSProperties, FC } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Node,
|
||||
Position,
|
||||
Connection,
|
||||
Edge,
|
||||
NodeProps,
|
||||
NodeTypes,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
sourcePosition: Position.Right,
|
||||
type: 'input',
|
||||
data: { label: 'Input' },
|
||||
position: { x: 0, y: 80 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'a',
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
data: { label: 'A Node' },
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
const buttonStyle: CSSProperties = { position: 'absolute', right: 10, top: 30, zIndex: 4 };
|
||||
|
||||
const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' };
|
||||
|
||||
const NodeA: FC<NodeProps> = () => {
|
||||
return <div style={nodeStyles}>A</div>;
|
||||
};
|
||||
|
||||
const NodeB: FC<NodeProps> = () => {
|
||||
return <div style={nodeStyles}>B</div>;
|
||||
};
|
||||
|
||||
type NodeTypesObject = {
|
||||
[key: string]: NodeTypes;
|
||||
};
|
||||
|
||||
const nodeTypesObjects: NodeTypesObject = {
|
||||
a: {
|
||||
a: NodeA,
|
||||
},
|
||||
b: {
|
||||
b: NodeB,
|
||||
},
|
||||
};
|
||||
|
||||
const NodeTypeChangeFlow = () => {
|
||||
const [nodeTypesId, setNodeTypesId] = useState<string>('a');
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const changeType = () => setNodeTypesId((nt) => (nt === 'a' ? 'b' : 'a'));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypesObjects[nodeTypesId]}
|
||||
>
|
||||
<button onClick={changeType} style={buttonStyle}>
|
||||
change type
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeTypeChangeFlow;
|
||||
211
packages/core/example/src/Overview/index.tsx
Normal file
211
packages/core/example/src/Overview/index.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import { MouseEvent as ReactMouseEvent, CSSProperties, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
Node,
|
||||
Viewport,
|
||||
SnapGrid,
|
||||
Connection,
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
OnSelectionChangeParams,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
||||
const onNodeDrag = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag', node, nodes);
|
||||
const onNodeDragStop = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
|
||||
const onNodeDoubleClick = (_: ReactMouseEvent, node: Node) => console.log('node double click', node);
|
||||
const onPaneClick = (event: ReactMouseEvent) => console.log('pane click', event);
|
||||
const onPaneScroll = (event?: ReactMouseEvent) => console.log('pane scroll', event);
|
||||
const onPaneContextMenu = (event: ReactMouseEvent) => console.log('pane context menu', event);
|
||||
const onSelectionDrag = (_: ReactMouseEvent, nodes: Node[]) => console.log('selection drag', nodes);
|
||||
const onSelectionDragStart = (_: ReactMouseEvent, nodes: Node[]) => console.log('selection drag start', nodes);
|
||||
const onSelectionDragStop = (_: ReactMouseEvent, nodes: Node[]) => console.log('selection drag stop', nodes);
|
||||
const onSelectionContextMenu = (event: ReactMouseEvent, nodes: Node[]) => {
|
||||
event.preventDefault();
|
||||
console.log('selection context menu', nodes);
|
||||
};
|
||||
const onNodeClick = (_: ReactMouseEvent, node: Node) => console.log('node click:', node);
|
||||
|
||||
const onSelectionChange = ({ nodes, edges }: OnSelectionChangeParams) => console.log('selection change', nodes, edges);
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
||||
console.log('pane ready:', reactFlowInstance);
|
||||
};
|
||||
|
||||
const onMoveStart = (_: MouseEvent | TouchEvent, viewport: Viewport) => console.log('zoom/move start', viewport);
|
||||
const onMoveEnd = (_: MouseEvent | TouchEvent, viewport: Viewport) => console.log('zoom/move end', viewport);
|
||||
const onEdgeContextMenu = (_: ReactMouseEvent, edge: Edge) => console.log('edge context menu', edge);
|
||||
const onEdgeMouseEnter = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse enter', edge);
|
||||
const onEdgeMouseMove = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse move', edge);
|
||||
const onEdgeMouseLeave = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse leave', edge);
|
||||
const onEdgeDoubleClick = (_: ReactMouseEvent, edge: Edge) => console.log('edge double click', edge);
|
||||
const onNodesDelete = (nodes: Node[]) => console.log('nodes delete', nodes);
|
||||
const onEdgesDelete = (edges: Edge[]) => console.log('edges delete', edges);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
Welcome to <strong>React Flow!</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
This is a <strong>default node</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
This one has a <strong>custom style</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 400, y: 100 },
|
||||
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
position: { x: 250, y: 200 },
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
You can find the docs on{' '}
|
||||
<a href="https://github.com/wbkd/react-flow" target="_blank" rel="noopener noreferrer">
|
||||
Github
|
||||
</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
Or check out the other <strong>examples</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 250, y: 325 },
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'output',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
An <strong>output node</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 100, y: 480 },
|
||||
},
|
||||
{ id: '7', type: 'output', data: { label: 'Another output node' }, position: { x: 400, y: 450 } },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' },
|
||||
{ id: 'e4-5', source: '4', target: '5', label: 'edge with arrow head' },
|
||||
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', label: 'smooth step edge' },
|
||||
{
|
||||
id: 'e5-7',
|
||||
source: '5',
|
||||
target: '7',
|
||||
type: 'step',
|
||||
style: { stroke: '#f6ab6c' },
|
||||
label: 'a step edge',
|
||||
animated: true,
|
||||
labelStyle: { fill: '#f6ab6c', fontWeight: 700 },
|
||||
},
|
||||
];
|
||||
|
||||
const connectionLineStyle: CSSProperties = { stroke: '#ddd' };
|
||||
const snapGrid: SnapGrid = [25, 25];
|
||||
|
||||
const nodeStrokeColor = (n: Node): string => {
|
||||
if (n.style?.background) return n.style.background as string;
|
||||
if (n.type === 'input') return '#0041d0';
|
||||
if (n.type === 'output') return '#ff0072';
|
||||
if (n.type === 'default') return '#1a192b';
|
||||
|
||||
return '#eee';
|
||||
};
|
||||
|
||||
const nodeColor = (n: Node): string => {
|
||||
if (n.style?.background) return n.style.background as string;
|
||||
|
||||
return '#fff';
|
||||
};
|
||||
|
||||
const OverviewFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onPaneClick={onPaneClick}
|
||||
onPaneScroll={onPaneScroll}
|
||||
onPaneContextMenu={onPaneContextMenu}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onNodeDoubleClick={onNodeDoubleClick}
|
||||
onSelectionDragStart={onSelectionDragStart}
|
||||
onSelectionDrag={onSelectionDrag}
|
||||
onSelectionDragStop={onSelectionDragStop}
|
||||
onSelectionContextMenu={onSelectionContextMenu}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onMoveStart={onMoveStart}
|
||||
onMoveEnd={onMoveEnd}
|
||||
onInit={onInit}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
snapToGrid={true}
|
||||
snapGrid={snapGrid}
|
||||
onEdgeContextMenu={onEdgeContextMenu}
|
||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||
onEdgeMouseMove={onEdgeMouseMove}
|
||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.2 }}
|
||||
attributionPosition="top-right"
|
||||
maxZoom={Infinity}
|
||||
onNodesDelete={onNodesDelete}
|
||||
onEdgesDelete={onEdgesDelete}
|
||||
>
|
||||
<MiniMap nodeStrokeColor={nodeStrokeColor} nodeColor={nodeColor} nodeBorderRadius={2} />
|
||||
<Controls />
|
||||
<Background color="#aaa" gap={25} />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewFlow;
|
||||
36
packages/core/example/src/Provider/Sidebar.tsx
Normal file
36
packages/core/example/src/Provider/Sidebar.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useStore, useStoreApi } from 'react-flow-renderer';
|
||||
|
||||
const Sidebar = () => {
|
||||
const store = useStoreApi();
|
||||
const nodeInternals = useStore((store) => store.nodeInternals);
|
||||
const transform = useStore((store) => store.transform);
|
||||
|
||||
const selectAll = () => {
|
||||
nodeInternals.forEach((node) => (node.selected = true));
|
||||
store.setState({ nodeInternals: new Map(nodeInternals) });
|
||||
};
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<div className="description">
|
||||
This is an example of how you can access the internal state outside of the ReactFlow component.
|
||||
</div>
|
||||
<div className="title">Zoom & pan transform</div>
|
||||
<div className="transform">
|
||||
[{transform[0].toFixed(2)}, {transform[1].toFixed(2)}, {transform[2].toFixed(2)}]
|
||||
</div>
|
||||
<div className="title">Nodes</div>
|
||||
{Array.from(nodeInternals).map(([, node]) => (
|
||||
<div key={node.id}>
|
||||
Node {node.id} - x: {node.position.x.toFixed(2)}, y: {node.position.y.toFixed(2)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="selectall">
|
||||
<button onClick={selectAll}>select all nodes</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
62
packages/core/example/src/Provider/index.tsx
Normal file
62
packages/core/example/src/Provider/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
Node,
|
||||
Controls,
|
||||
Connection,
|
||||
Edge,
|
||||
ConnectionMode,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
ReactFlowInstance,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import Sidebar from './Sidebar';
|
||||
|
||||
import './provider.css';
|
||||
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('pane ready:', reactFlowInstance);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ 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 initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
const ProviderFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Edge | Connection) => setEdges((els) => addEdge(params, els));
|
||||
|
||||
return (
|
||||
<div className="providerflow">
|
||||
<ReactFlowProvider>
|
||||
<Sidebar />
|
||||
<div className="reactflow-wrapper">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onInit={onInit}
|
||||
connectionMode={ConnectionMode.Loose}
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProviderFlow;
|
||||
45
packages/core/example/src/Provider/provider.css
Normal file
45
packages/core/example/src/Provider/provider.css
Normal file
@@ -0,0 +1,45 @@
|
||||
.providerflow {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.providerflow aside {
|
||||
border-right: 1px solid #eee;
|
||||
padding: 15px 10px;
|
||||
font-size: 12px;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
.providerflow aside .description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.providerflow aside .title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.providerflow aside .transform {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.providerflow .reactflow-wrapper {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.providerflow .selectall {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.providerflow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.providerflow aside {
|
||||
width: 20%;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
63
packages/core/example/src/SaveRestore/Controls.tsx
Normal file
63
packages/core/example/src/SaveRestore/Controls.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React, { memo, useCallback, Dispatch, FC } from 'react';
|
||||
import { useReactFlow, ReactFlowInstance, Edge, Node, ReactFlowJsonObject } from 'react-flow-renderer';
|
||||
import localforage from 'localforage';
|
||||
|
||||
localforage.config({
|
||||
name: 'react-flow',
|
||||
storeName: 'flows',
|
||||
});
|
||||
|
||||
const flowKey = 'example-flow';
|
||||
|
||||
const getNodeId = () => `randomnode_${+new Date()}`;
|
||||
|
||||
type ControlsProps = {
|
||||
rfInstance?: ReactFlowInstance;
|
||||
setNodes: Dispatch<React.SetStateAction<Node<any>[]>>;
|
||||
setEdges: Dispatch<React.SetStateAction<Edge<any>[]>>;
|
||||
};
|
||||
|
||||
const Controls: FC<ControlsProps> = ({ rfInstance, setNodes, setEdges }) => {
|
||||
const { setViewport } = useReactFlow();
|
||||
|
||||
const onSave = useCallback(() => {
|
||||
if (rfInstance) {
|
||||
const flow = rfInstance.toObject();
|
||||
localforage.setItem(flowKey, flow);
|
||||
}
|
||||
}, [rfInstance]);
|
||||
|
||||
const onRestore = useCallback(() => {
|
||||
const restoreFlow = async () => {
|
||||
const flow: ReactFlowJsonObject | null = await localforage.getItem(flowKey);
|
||||
|
||||
if (flow) {
|
||||
const { x, y, zoom } = flow.viewport;
|
||||
setNodes(flow.nodes || []);
|
||||
setEdges(flow.edges || []);
|
||||
setViewport({ x, y, zoom: zoom || 0 });
|
||||
}
|
||||
};
|
||||
|
||||
restoreFlow();
|
||||
}, [setNodes, setEdges, setViewport]);
|
||||
|
||||
const onAdd = useCallback(() => {
|
||||
const newNode = {
|
||||
id: `random_node-${getNodeId()}`,
|
||||
data: { label: 'Added node' },
|
||||
position: { x: Math.random() * window.innerWidth - 100, y: Math.random() * window.innerHeight },
|
||||
};
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
}, [setNodes]);
|
||||
|
||||
return (
|
||||
<div className="save__controls">
|
||||
<button onClick={onSave}>save</button>
|
||||
<button onClick={onRestore}>restore</button>
|
||||
<button onClick={onAdd}>add node</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Controls);
|
||||
46
packages/core/example/src/SaveRestore/index.tsx
Normal file
46
packages/core/example/src/SaveRestore/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useState } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
Node,
|
||||
addEdge,
|
||||
Connection,
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import Controls from './Controls';
|
||||
|
||||
import './save.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', data: { label: 'Node 1' }, position: { x: 100, y: 100 } },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||
|
||||
const SaveRestore = () => {
|
||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance>();
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onInit={setRfInstance}
|
||||
>
|
||||
<Controls rfInstance={rfInstance} setNodes={setNodes} setEdges={setEdges} />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveRestore;
|
||||
11
packages/core/example/src/SaveRestore/save.css
Normal file
11
packages/core/example/src/SaveRestore/save.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.save__controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 4;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.save__controls button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
86
packages/core/example/src/Stress/index.tsx
Normal file
86
packages/core/example/src/Stress/index.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useState, CSSProperties, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
ReactFlowInstance,
|
||||
Edge,
|
||||
Node,
|
||||
NodeChange,
|
||||
applyNodeChanges,
|
||||
Connection,
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
EdgeChange,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import { getNodesAndEdges } from './utils';
|
||||
|
||||
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 };
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
||||
reactFlowInstance.fitView();
|
||||
console.log(reactFlowInstance.getNodes());
|
||||
};
|
||||
|
||||
const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(25, 25);
|
||||
|
||||
const StressFlow = () => {
|
||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||
const onConnect = useCallback((connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
}, []);
|
||||
const updatePos = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
return {
|
||||
...n,
|
||||
position: {
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const updateElements = () => {
|
||||
const grid = Math.ceil(Math.random() * 10);
|
||||
const initialElements = getNodesAndEdges(grid, grid);
|
||||
setNodes(initialElements.nodes);
|
||||
setEdges(initialElements.edges);
|
||||
};
|
||||
|
||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
||||
}, []);
|
||||
|
||||
const onEdgeChange = useCallback((changes: EdgeChange[]) => {
|
||||
setEdges((es) => applyEdgeChanges(changes, es));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onInit={onInit}
|
||||
onConnect={onConnect}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgeChange}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
|
||||
<div style={buttonWrapperStyles}>
|
||||
<button onClick={updatePos} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={updateElements}>update elements</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default StressFlow;
|
||||
39
packages/core/example/src/Stress/utils.ts
Normal file
39
packages/core/example/src/Stress/utils.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Node, Edge } from 'react-flow-renderer';
|
||||
|
||||
type ElementsCollection = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
};
|
||||
|
||||
export function getNodesAndEdges(xElements: number = 10, yElements: number = 10): ElementsCollection {
|
||||
const initialNodes = [];
|
||||
const initialEdges: Edge[] = [];
|
||||
let nodeId = 1;
|
||||
let recentNodeId = null;
|
||||
|
||||
for (let y = 0; y < yElements; y++) {
|
||||
for (let x = 0; x < xElements; x++) {
|
||||
const position = { x: x * 100, y: y * 50 };
|
||||
const data = { label: `Node ${nodeId}` };
|
||||
const node = {
|
||||
id: nodeId.toString(),
|
||||
style: { width: 50, fontSize: 11 },
|
||||
data,
|
||||
position,
|
||||
};
|
||||
initialNodes.push(node);
|
||||
|
||||
if (recentNodeId && nodeId <= xElements * yElements) {
|
||||
initialEdges.push({ id: `${x}-${y}`, source: recentNodeId.toString(), target: nodeId.toString() });
|
||||
}
|
||||
|
||||
recentNodeId = nodeId;
|
||||
nodeId++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodes: initialNodes,
|
||||
edges: initialEdges,
|
||||
};
|
||||
}
|
||||
27
packages/core/example/src/Subflow/DebugNode.tsx
Normal file
27
packages/core/example/src/Subflow/DebugNode.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { memo, FC, CSSProperties } from 'react';
|
||||
|
||||
import { Handle, NodeProps, Position } from 'react-flow-renderer';
|
||||
|
||||
const infoStyle: CSSProperties = { fontSize: 11 };
|
||||
const idStyle: CSSProperties = {
|
||||
fontSize: 10,
|
||||
color: '#888899',
|
||||
position: 'absolute',
|
||||
top: 2,
|
||||
left: 2,
|
||||
};
|
||||
|
||||
const ColorSelectorNode: FC<NodeProps> = ({ zIndex, xPos, yPos, id }) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Top} />
|
||||
<div style={idStyle}>{id}</div>
|
||||
<div style={infoStyle}>
|
||||
x:{Math.round(xPos || 0)} y:{Math.round(yPos || 0)} z:{zIndex}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Bottom} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ColorSelectorNode);
|
||||
202
packages/core/example/src/Subflow/index.tsx
Normal file
202
packages/core/example/src/Subflow/index.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import { useState, MouseEvent, useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
MarkerType,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
import DebugNode from './DebugNode';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node, nodes: Node[]) => console.log('drag stop', node, nodes);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 100, y: 200 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255,50, 50, 0.5)', width: 500, height: 300 },
|
||||
},
|
||||
{
|
||||
id: '4a',
|
||||
data: { label: 'Node 4a' },
|
||||
position: { x: 15, y: 15 },
|
||||
className: 'light',
|
||||
parentNode: '4',
|
||||
extent: [
|
||||
[0, 0],
|
||||
[100, 100],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '4b',
|
||||
data: { label: 'Node 4b' },
|
||||
position: { x: 100, y: 60 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(50, 50, 255, 0.5)', height: 200, width: 300 },
|
||||
parentNode: '4',
|
||||
},
|
||||
{
|
||||
id: '4b1',
|
||||
data: { label: 'Node 4b1' },
|
||||
position: { x: 40, y: 20 },
|
||||
className: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
{
|
||||
id: '4b2',
|
||||
data: { label: 'Node 4b2' },
|
||||
position: { x: 20, y: 100 },
|
||||
className: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'group',
|
||||
data: { label: 'Node 5' },
|
||||
position: { x: 650, y: 250 },
|
||||
className: 'light',
|
||||
style: { width: 400, height: 150 },
|
||||
zIndex: 1000,
|
||||
},
|
||||
{
|
||||
id: '5a',
|
||||
data: { label: 'Node 5a' },
|
||||
position: { x: 25, y: 50 },
|
||||
className: 'light',
|
||||
parentNode: '5',
|
||||
},
|
||||
{
|
||||
id: '5b',
|
||||
data: { label: 'Node 5b' },
|
||||
position: { x: 225, y: 50 },
|
||||
className: 'light',
|
||||
parentNode: '5',
|
||||
},
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{
|
||||
id: 'e1-2',
|
||||
source: '1',
|
||||
target: '2',
|
||||
markerEnd: { type: MarkerType.Arrow, strokeWidth: 2, width: 15, height: 15, color: '#f00' },
|
||||
},
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e3-4', source: '3', target: '4', zIndex: 100 },
|
||||
{ id: 'e3-4b', source: '3', target: '4b' },
|
||||
{ id: 'e4a-4b1', source: '4a', target: '4b1' },
|
||||
{ id: 'e4a-4b2', source: '4a', target: '4b2', zIndex: 100 },
|
||||
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
||||
];
|
||||
|
||||
const nodeTypes = {
|
||||
default: DebugNode,
|
||||
};
|
||||
|
||||
const Subflow = () => {
|
||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
|
||||
const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||
|
||||
const updatePos = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
if (!n.parentNode) {
|
||||
n.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
};
|
||||
}
|
||||
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const logToObject = () => console.log(rfInstance?.toObject());
|
||||
const resetTransform = () => rfInstance?.setViewport({ x: 0, y: 0, zoom: 1 });
|
||||
|
||||
const toggleClassnames = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.className = n.className === 'light' ? 'dark' : 'light';
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const toggleChildNodes = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.hidden = !!n.parentNode && !n.hidden;
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onInit={onInit}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
className="react-flow-basic-example"
|
||||
defaultZoom={1.5}
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
onlyRenderVisibleElements={false}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
reset transform
|
||||
</button>
|
||||
<button onClick={updatePos} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
|
||||
toggle classnames
|
||||
</button>
|
||||
<button style={{ marginRight: 5 }} onClick={toggleChildNodes}>
|
||||
toggleChildNodes
|
||||
</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default Subflow;
|
||||
73
packages/core/example/src/Switch/index.tsx
Normal file
73
packages/core/example/src/Switch/index.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import ReactFlow, { addEdge, Node, Connection, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const nodesA: Node[] = [
|
||||
{ id: '1a', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2a', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3a', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4a', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const edgesA: Edge[] = [
|
||||
{ id: 'e1-2', source: '1a', target: '2a' },
|
||||
{ id: 'e1-3', source: '1a', target: '3a' },
|
||||
];
|
||||
|
||||
const nodesB: Node[] = [
|
||||
{ id: 'inputb', type: 'input', data: { label: 'Input' }, position: { x: 300, y: 5 }, className: 'light' },
|
||||
{ id: '1b', data: { label: 'Node 1' }, position: { x: 0, y: 100 }, className: 'light' },
|
||||
{ id: '2b', data: { label: 'Node 2' }, position: { x: 200, y: 100 }, className: 'light' },
|
||||
{ id: '3b', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4b', data: { label: 'Node 4' }, position: { x: 600, y: 100 }, className: 'light' },
|
||||
];
|
||||
|
||||
const edgesB: Edge[] = [
|
||||
{ id: 'e1b', source: 'inputb', target: '1b' },
|
||||
{ id: 'e2b', source: 'inputb', target: '2b' },
|
||||
{ id: 'e3b', source: 'inputb', target: '3b' },
|
||||
{ id: 'e4b', source: 'inputb', target: '4b' },
|
||||
];
|
||||
|
||||
const BasicFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(nodesA);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(edgesA);
|
||||
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
>
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setNodes(nodesA);
|
||||
setEdges(edgesA);
|
||||
}}
|
||||
style={{ marginRight: 5 }}
|
||||
>
|
||||
flow a
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setNodes(nodesB);
|
||||
setEdges(edgesB);
|
||||
}}
|
||||
>
|
||||
flow b
|
||||
</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicFlow;
|
||||
58
packages/core/example/src/TouchDevice/index.tsx
Normal file
58
packages/core/example/src/TouchDevice/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Position,
|
||||
Connection,
|
||||
addEdge,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import './touch-device.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 100, y: 100 },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 300, y: 100 },
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [];
|
||||
|
||||
const TouchDeviceFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), [setEdges]);
|
||||
const onConnectStart = useCallback(() => console.log('connect start'), []);
|
||||
const onConnectStop = useCallback(() => console.log('connect end'), []);
|
||||
const onClickConnectStart = useCallback(() => console.log('click connect start'), []);
|
||||
const onClickConnectStop = useCallback(() => console.log('click connect end'), []);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onConnect={onConnect}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectStop={onConnectStop}
|
||||
onClickConnectStart={onClickConnectStart}
|
||||
onClickConnectStop={onClickConnectStop}
|
||||
className="touchdevice-flow"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TouchDeviceFlow;
|
||||
19
packages/core/example/src/TouchDevice/touch-device.css
Normal file
19
packages/core/example/src/TouchDevice/touch-device.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.touchdevice-flow .react-flow__handle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
background-color: #9f7aea;
|
||||
}
|
||||
|
||||
.touchdevice-flow .react-flow__handle.connecting {
|
||||
animation: bounce 1600ms infinite ease-out;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0% {
|
||||
transform: translate(0, -50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, -50%) scale(1.1);
|
||||
}
|
||||
}
|
||||
19
packages/core/example/src/Undirectional/CustomNode.tsx
Normal file
19
packages/core/example/src/Undirectional/CustomNode.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { memo, FC, CSSProperties } from 'react';
|
||||
|
||||
import { Handle, Position, NodeProps } from 'react-flow-renderer';
|
||||
|
||||
const nodeStyles: CSSProperties = { padding: '10px 15px', border: '1px solid #ddd' };
|
||||
|
||||
const CustomNode: FC<NodeProps> = ({ id }) => {
|
||||
return (
|
||||
<div style={nodeStyles}>
|
||||
<div>node {id}</div>
|
||||
<Handle type="source" id="left" position={Position.Left} />
|
||||
<Handle type="source" id="right" position={Position.Right} />
|
||||
<Handle type="source" id="top" position={Position.Top} />
|
||||
<Handle type="source" id="bottom" position={Position.Bottom} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CustomNode);
|
||||
226
packages/core/example/src/Undirectional/index.tsx
Normal file
226
packages/core/example/src/Undirectional/index.tsx
Normal file
@@ -0,0 +1,226 @@
|
||||
import React, { MouseEvent, useCallback } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
useReactFlow,
|
||||
NodeTypes,
|
||||
addEdge,
|
||||
ReactFlowProvider,
|
||||
Node,
|
||||
Connection,
|
||||
Edge,
|
||||
ConnectionLineType,
|
||||
ConnectionMode,
|
||||
updateEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
import CustomNode from './CustomNode';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '00',
|
||||
type: 'custom',
|
||||
position: { x: 300, y: 250 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '01',
|
||||
type: 'custom',
|
||||
position: { x: 100, y: 50 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '02',
|
||||
type: 'custom',
|
||||
position: { x: 500, y: 50 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '03',
|
||||
type: 'custom',
|
||||
position: { x: 500, y: 500 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '04',
|
||||
type: 'custom',
|
||||
position: { x: 100, y: 500 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
type: 'custom',
|
||||
position: { x: 300, y: 5 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '20',
|
||||
type: 'custom',
|
||||
position: { x: 600, y: 250 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '30',
|
||||
type: 'custom',
|
||||
position: { x: 300, y: 600 },
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
id: '40',
|
||||
type: 'custom',
|
||||
position: { x: 5, y: 250 },
|
||||
data: null,
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{
|
||||
id: 'e0-1a',
|
||||
source: '00',
|
||||
target: '01',
|
||||
sourceHandle: 'left',
|
||||
targetHandle: 'bottom',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-1b',
|
||||
source: '00',
|
||||
target: '01',
|
||||
sourceHandle: 'top',
|
||||
targetHandle: 'right',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-2a',
|
||||
source: '00',
|
||||
target: '02',
|
||||
sourceHandle: 'top',
|
||||
targetHandle: 'left',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-2b',
|
||||
source: '00',
|
||||
target: '02',
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'bottom',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-3a',
|
||||
source: '00',
|
||||
target: '03',
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'top',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-3b',
|
||||
source: '00',
|
||||
target: '03',
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'left',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-4a',
|
||||
source: '00',
|
||||
target: '04',
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'right',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-4b',
|
||||
source: '00',
|
||||
target: '04',
|
||||
sourceHandle: 'left',
|
||||
targetHandle: 'top',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-10',
|
||||
source: '00',
|
||||
target: '10',
|
||||
sourceHandle: 'top',
|
||||
targetHandle: 'bottom',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-20',
|
||||
source: '00',
|
||||
target: '20',
|
||||
sourceHandle: 'right',
|
||||
targetHandle: 'left',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-30',
|
||||
source: '00',
|
||||
target: '30',
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'top',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
id: 'e0-40',
|
||||
source: '00',
|
||||
target: '40',
|
||||
sourceHandle: 'left',
|
||||
targetHandle: 'right',
|
||||
type: 'default',
|
||||
},
|
||||
];
|
||||
|
||||
const nodeTypes: NodeTypes = {
|
||||
custom: CustomNode,
|
||||
};
|
||||
|
||||
let id = 4;
|
||||
const getId = () => `${id++}`;
|
||||
|
||||
const UpdateNodeInternalsFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = (params: Edge | Connection) => setEdges((els) => addEdge(params, els));
|
||||
const { project } = useReactFlow();
|
||||
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
|
||||
const onPaneClick = useCallback(
|
||||
(evt: MouseEvent) =>
|
||||
setNodes((nds) =>
|
||||
nds.concat({
|
||||
id: getId(),
|
||||
position: project({ x: evt.clientX, y: evt.clientY - 40 }),
|
||||
type: 'custom',
|
||||
data: null,
|
||||
})
|
||||
),
|
||||
[project, setNodes]
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
nodeTypes={nodeTypes}
|
||||
onConnect={onConnect}
|
||||
onPaneClick={onPaneClick}
|
||||
connectionLineType={ConnectionLineType.Bezier}
|
||||
connectionMode={ConnectionMode.Loose}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const WrappedFlow = () => (
|
||||
<ReactFlowProvider>
|
||||
<UpdateNodeInternalsFlow />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
|
||||
export default WrappedFlow;
|
||||
97
packages/core/example/src/UpdatableEdge/index.tsx
Normal file
97
packages/core/example/src/UpdatableEdge/index.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
Controls,
|
||||
updateEdge,
|
||||
addEdge,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
NodeChange,
|
||||
EdgeChange,
|
||||
HandleType,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
Node <strong>A</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
Node <strong>B</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
Node <strong>C</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 400, y: 100 },
|
||||
style: { background: '#D6D5E6', color: '#333', border: '1px solid #222138', width: 180 },
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }];
|
||||
|
||||
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||
const onEdgeUpdateStart = (_: React.MouseEvent, edge: Edge, handleType: HandleType) =>
|
||||
console.log(`start update ${handleType} handle`, edge);
|
||||
const onEdgeUpdateEnd = (_: MouseEvent, edge: Edge, handleType: HandleType) =>
|
||||
console.log(`end update ${handleType} handle`, edge);
|
||||
|
||||
const UpdatableEdge = () => {
|
||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
const onConnect = (connection: Connection) => setEdges((els) => addEdge(connection, els));
|
||||
|
||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||
console.log(changes);
|
||||
setNodes((ns) => applyNodeChanges(changes, ns));
|
||||
}, []);
|
||||
|
||||
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
||||
setEdges((es) => applyEdgeChanges(changes, es));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onInit={onInit}
|
||||
snapToGrid={true}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
onConnect={onConnect}
|
||||
// onEdgeUpdateStart={onEdgeUpdateStart}
|
||||
// onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdatableEdge;
|
||||
89
packages/core/example/src/UpdateNode/index.tsx
Normal file
89
packages/core/example/src/UpdateNode/index.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactFlow, { Node, Edge, useNodesState, useEdgesState } from 'react-flow-renderer';
|
||||
|
||||
import './updatenode.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', data: { label: '-' }, position: { x: 100, y: 100 } },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 200 } },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||
|
||||
const UpdateNode = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const [nodeName, setNodeName] = useState<string>('Node 1');
|
||||
const [nodeBg, setNodeBg] = useState<string>('#eee');
|
||||
const [nodeHidden, setNodeHidden] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setNodes((nds) =>
|
||||
nds.map((n) => {
|
||||
if (n.id === '1') {
|
||||
// it's important that you create a new object here in order to notify react flow about the change
|
||||
n.data = {
|
||||
...n.data,
|
||||
label: nodeName,
|
||||
};
|
||||
}
|
||||
|
||||
return n;
|
||||
})
|
||||
);
|
||||
}, [nodeName]);
|
||||
|
||||
useEffect(() => {
|
||||
setNodes((nds) =>
|
||||
nds.map((n) => {
|
||||
if (n.id === '1') {
|
||||
// it's important that you create a new object here in order to notify react flow about the change
|
||||
n.style = { ...n.style, backgroundColor: nodeBg };
|
||||
}
|
||||
|
||||
return n;
|
||||
})
|
||||
);
|
||||
}, [nodeBg]);
|
||||
|
||||
useEffect(() => {
|
||||
setNodes((nds) =>
|
||||
nds.map((n) => {
|
||||
if (n.id === '1' || n.id === 'e1-2') {
|
||||
// when you update a simple type you can just update the value
|
||||
n.hidden = nodeHidden;
|
||||
}
|
||||
|
||||
return n;
|
||||
})
|
||||
);
|
||||
}, [nodeHidden]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
defaultZoom={1.5}
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
>
|
||||
<div className="updatenode__controls">
|
||||
<label>label:</label>
|
||||
<input value={nodeName} onChange={(evt) => setNodeName(evt.target.value)} />
|
||||
|
||||
<label className="updatenode__bglabel">background:</label>
|
||||
<input value={nodeBg} onChange={(evt) => setNodeBg(evt.target.value)} />
|
||||
|
||||
<div className="updatenode__checkboxwrapper">
|
||||
<label>hidden:</label>
|
||||
<input type="checkbox" checked={nodeHidden} onChange={(evt) => setNodeHidden(evt.target.checked)} />
|
||||
</div>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateNode;
|
||||
21
packages/core/example/src/UpdateNode/updatenode.css
Normal file
21
packages/core/example/src/UpdateNode/updatenode.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.updatenode__controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 4;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.updatenode__controls label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.updatenode__bglabel {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.updatenode__checkboxwrapper {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
11
packages/core/example/src/UseKeyPress/index.tsx
Normal file
11
packages/core/example/src/UseKeyPress/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useKeyPress } from 'react-flow-renderer';
|
||||
|
||||
const UseKeyPressComponent = () => {
|
||||
const metaPressed = useKeyPress(['Meta']);
|
||||
|
||||
console.log({ metaPressed });
|
||||
|
||||
return <div />;
|
||||
};
|
||||
|
||||
export default UseKeyPressComponent;
|
||||
130
packages/core/example/src/UseReactFlow/index.tsx
Normal file
130
packages/core/example/src/UseReactFlow/index.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { useCallback, MouseEvent, useEffect } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
addEdge,
|
||||
Background,
|
||||
MiniMap,
|
||||
useReactFlow,
|
||||
ReactFlowProvider,
|
||||
Connection,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' },
|
||||
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' },
|
||||
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' },
|
||||
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' },
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
];
|
||||
|
||||
let id = 5;
|
||||
|
||||
const getId = () => `${id++}`;
|
||||
|
||||
const UseZoomPanHelperFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
||||
const {
|
||||
project,
|
||||
setCenter,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitView,
|
||||
addNodes,
|
||||
setNodes: setNodesHook,
|
||||
addEdges,
|
||||
getNodes,
|
||||
getEdges,
|
||||
} = useReactFlow();
|
||||
|
||||
const onPaneClick = useCallback(
|
||||
(evt: MouseEvent) => {
|
||||
const projectedPosition = project({ x: evt.clientX, y: evt.clientY - 40 });
|
||||
|
||||
setNodes((nds) =>
|
||||
nds.concat({
|
||||
id: getId(),
|
||||
position: projectedPosition,
|
||||
data: {
|
||||
label: `${projectedPosition.x}-${projectedPosition.y}`,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[project, setNodes]
|
||||
);
|
||||
|
||||
const onNodeClick = useCallback(
|
||||
(_: MouseEvent, node: Node) => {
|
||||
const { x, y } = node.position;
|
||||
setCenter(x, y, { zoom: 1, duration: 1200 });
|
||||
},
|
||||
[setCenter]
|
||||
);
|
||||
|
||||
const onAddNode = useCallback(() => {
|
||||
const newNode = {
|
||||
id: getId(),
|
||||
position: { x: Math.random() * 500, y: Math.random() * 500 },
|
||||
data: {
|
||||
label: 'New Node',
|
||||
},
|
||||
};
|
||||
|
||||
addNodes(newNode);
|
||||
}, [addNodes]);
|
||||
|
||||
const logNodes = useCallback(() => {
|
||||
console.log('nodes', getNodes());
|
||||
console.log('edges', getEdges());
|
||||
}, [getNodes]);
|
||||
|
||||
useEffect(() => {
|
||||
addEdges({ id: 'e3-4', source: '3', target: '4' });
|
||||
}, [addEdges]);
|
||||
|
||||
const onResetNodes = useCallback(() => setNodesHook(initialNodes), [setNodesHook]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onPaneClick={onPaneClick}
|
||||
fitView
|
||||
fitViewOptions={{ duration: 1200, padding: 0.2 }}
|
||||
maxZoom={Infinity}
|
||||
>
|
||||
<div style={{ position: 'absolute', left: 0, top: 0, zIndex: 100 }}>
|
||||
<button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button>
|
||||
<button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button>
|
||||
<button onClick={() => fitView({ duration: 1200, padding: 0.3 })}>fitView</button>
|
||||
<button onClick={onAddNode}>add node</button>
|
||||
<button onClick={onResetNodes}>reset nodes</button>
|
||||
<button onClick={logNodes}>useNodes</button>
|
||||
</div>
|
||||
<Background />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
const WrappedFlow = () => (
|
||||
<ReactFlowProvider>
|
||||
<UseZoomPanHelperFlow />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
|
||||
export default WrappedFlow;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React, { memo, FC, useMemo, CSSProperties } from 'react';
|
||||
import { Handle, Position, NodeProps } from 'react-flow-renderer';
|
||||
|
||||
const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' };
|
||||
|
||||
const CustomNode: FC<NodeProps> = ({ data }) => {
|
||||
const handles = useMemo(
|
||||
() =>
|
||||
Array.from({ length: data.handleCount }, (x, i) => {
|
||||
const handleId = `handle-${i}`;
|
||||
return (
|
||||
<Handle
|
||||
key={handleId}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={handleId}
|
||||
style={{ top: 10 * i + data.handlePosition * 10 }}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
[data.handleCount, data.handlePosition]
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={nodeStyles}>
|
||||
<Handle type="target" position={Position.Left} />
|
||||
<div>output handle count: {data.handleCount}</div>
|
||||
{handles}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CustomNode);
|
||||
103
packages/core/example/src/UseUpdateNodeInternals/index.tsx
Normal file
103
packages/core/example/src/UseUpdateNodeInternals/index.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useCallback, CSSProperties, MouseEvent } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
NodeTypes,
|
||||
addEdge,
|
||||
useReactFlow,
|
||||
ReactFlowProvider,
|
||||
Node,
|
||||
Connection,
|
||||
Edge,
|
||||
useUpdateNodeInternals,
|
||||
Position,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
import CustomNode from './CustomNode';
|
||||
|
||||
const initialHandleCount = 1;
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
data: { label: 'Node 1', handleCount: initialHandleCount, handlePosition: 0 },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
];
|
||||
|
||||
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 10 };
|
||||
|
||||
const nodeTypes: NodeTypes = {
|
||||
custom: CustomNode,
|
||||
};
|
||||
|
||||
let id = 5;
|
||||
const getId = (): string => `${id++}`;
|
||||
|
||||
const UpdateNodeInternalsFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const onConnect = (params: Edge | Connection) => setEdges((els) => addEdge(params, els));
|
||||
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { project } = useReactFlow();
|
||||
|
||||
const onPaneClick = useCallback(
|
||||
(evt: MouseEvent) =>
|
||||
setNodes((nds) =>
|
||||
nds.concat({
|
||||
id: getId(),
|
||||
position: project({ x: evt.clientX, y: evt.clientY - 40 }),
|
||||
data: { label: 'new node' },
|
||||
targetPosition: Position.Left,
|
||||
sourcePosition: Position.Right,
|
||||
})
|
||||
),
|
||||
[project, setNodes]
|
||||
);
|
||||
|
||||
const toggleHandleCount = useCallback(() => {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
return { ...node, data: { ...node.data, handleCount: node.data?.handleCount === 1 ? 2 : 1 } };
|
||||
})
|
||||
);
|
||||
}, [setNodes]);
|
||||
|
||||
const toggleHandlePosition = useCallback(() => {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
return { ...node, data: { ...node.data, handlePosition: node.data?.handlePosition === 0 ? 1 : 0 } };
|
||||
})
|
||||
);
|
||||
}, [setNodes]);
|
||||
|
||||
const updateNode = useCallback(() => updateNodeInternals('1'), [updateNodeInternals]);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
nodeTypes={nodeTypes}
|
||||
onConnect={onConnect}
|
||||
onPaneClick={onPaneClick}
|
||||
>
|
||||
<div style={buttonWrapperStyles}>
|
||||
<button onClick={toggleHandleCount}>toggle handle count</button>
|
||||
<button onClick={toggleHandlePosition}>toggle handle position</button>
|
||||
<button onClick={updateNode}>update node internals</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
const WrappedFlow = () => (
|
||||
<ReactFlowProvider>
|
||||
<UpdateNodeInternalsFlow />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
|
||||
export default WrappedFlow;
|
||||
78
packages/core/example/src/Validation/index.tsx
Normal file
78
packages/core/example/src/Validation/index.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { MouseEvent as ReactMouseEvent, FC } from 'react';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Handle,
|
||||
Connection,
|
||||
Position,
|
||||
Node,
|
||||
Edge,
|
||||
OnConnectStartParams,
|
||||
NodeProps,
|
||||
NodeTypes,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import './validation.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{ id: '0', type: 'custominput', position: { x: 0, y: 150 }, data: null },
|
||||
{ id: 'A', type: 'customnode', position: { x: 250, y: 0 }, data: null },
|
||||
{ id: 'B', type: 'customnode', position: { x: 250, y: 150 }, data: null },
|
||||
{ id: 'C', type: 'customnode', position: { x: 250, y: 300 }, data: null },
|
||||
];
|
||||
|
||||
const isValidConnection = (connection: Connection) => connection.target === 'B';
|
||||
const onConnectStart = (_: ReactMouseEvent, { nodeId, handleType }: OnConnectStartParams) =>
|
||||
console.log('on connect start', { nodeId, handleType });
|
||||
const onConnectStop = (event: MouseEvent) => console.log('on connect stop', event);
|
||||
const onConnectEnd = (event: MouseEvent) => console.log('on connect end', event);
|
||||
|
||||
const CustomInput: FC<NodeProps> = () => (
|
||||
<>
|
||||
<div>Only connectable with B</div>
|
||||
<Handle type="source" position={Position.Right} isValidConnection={isValidConnection} />
|
||||
</>
|
||||
);
|
||||
|
||||
const CustomNode: FC<NodeProps> = ({ id }) => (
|
||||
<>
|
||||
<Handle type="target" position={Position.Left} isValidConnection={isValidConnection} />
|
||||
<div>{id}</div>
|
||||
<Handle type="source" position={Position.Right} isValidConnection={isValidConnection} />
|
||||
</>
|
||||
);
|
||||
|
||||
const nodeTypes: NodeTypes = {
|
||||
custominput: CustomInput,
|
||||
customnode: CustomNode,
|
||||
};
|
||||
|
||||
const ValidationFlow = () => {
|
||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const onConnect = (params: Connection | Edge) => {
|
||||
console.log('on connect', params);
|
||||
setEdges((eds) => addEdge(params, eds));
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
selectNodesOnDrag={false}
|
||||
className="validationflow"
|
||||
nodeTypes={nodeTypes}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectStop={onConnectStop}
|
||||
onConnectEnd={onConnectEnd}
|
||||
fitView
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ValidationFlow;
|
||||
31
packages/core/example/src/Validation/validation.css
Normal file
31
packages/core/example/src/Validation/validation.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.validationflow .react-flow__node {
|
||||
width: 150px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
color: #555;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__node-customnode {
|
||||
background: #e6e6e9;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.react-flow__node-custominput .react-flow__handle {
|
||||
background: #e6e6e9;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__node-custominput {
|
||||
background: #fff;
|
||||
|
||||
}
|
||||
|
||||
.validationflow .react-flow__handle-connecting {
|
||||
background: #ff6060;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__handle-valid {
|
||||
background: #55dd99;
|
||||
}
|
||||
82
packages/core/example/src/index.css
Normal file
82
packages/core/example/src/index.css
Normal file
@@ -0,0 +1,82 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
header a,
|
||||
header a:focus,
|
||||
header a:active,
|
||||
header a:visited {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
header select {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.overview-example__add {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.react-flow__node a {
|
||||
font-weight: 700;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.react-flow__node.dark-node {
|
||||
background: #0041d0;
|
||||
color: #f8f8f8;
|
||||
}
|
||||
|
||||
.react-flow__node.dark {
|
||||
background: #557;
|
||||
color: #f8f8f8;
|
||||
}
|
||||
|
||||
.react-flow__node-selectorNode {
|
||||
font-size: 12px;
|
||||
background: #f0f2f3;
|
||||
border: 1px solid 555;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.react-flow__node-selectorNode .react-flow__handle {
|
||||
border-color: #f0f2f3;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.overview-example__add {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
203
packages/core/example/src/index.tsx
Normal file
203
packages/core/example/src/index.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
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';
|
||||
import CustomNode from './CustomNode';
|
||||
import DefaultNodes from './DefaultNodes';
|
||||
import DragHandle from './DragHandle';
|
||||
import DragNDrop from './DragNDrop';
|
||||
import Edges from './Edges';
|
||||
import EdgeTypes from './EdgeTypes';
|
||||
import Empty from './Empty';
|
||||
import FloatingEdges from './FloatingEdges';
|
||||
import Hidden from './Hidden';
|
||||
import './index.css';
|
||||
import Interaction from './Interaction';
|
||||
import Layouting from './Layouting';
|
||||
import MultiFlows from './MultiFlows';
|
||||
import NestedNodes from './NestedNodes';
|
||||
import NodeTypeChange from './NodeTypeChange';
|
||||
import NodeTypesObjectChange from './NodeTypesObjectChange';
|
||||
import Overview from './Overview';
|
||||
import Provider from './Provider';
|
||||
import SaveRestore from './SaveRestore';
|
||||
import Stress from './Stress';
|
||||
import Subflow from './Subflow';
|
||||
import SwitchFlow from './Switch';
|
||||
import TouchDevice from './TouchDevice';
|
||||
import Undirectional from './Undirectional';
|
||||
import UpdatableEdge from './UpdatableEdge';
|
||||
import UpdateNode from './UpdateNode';
|
||||
import UseUpdateNodeInternals from './UseUpdateNodeInternals';
|
||||
import UseReactFlow from './UseReactFlow';
|
||||
import Validation from './Validation';
|
||||
import UseKeyPress from './UseKeyPress';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
path: '/basic',
|
||||
component: Basic,
|
||||
},
|
||||
{
|
||||
path: '/default-nodes',
|
||||
component: DefaultNodes,
|
||||
},
|
||||
{
|
||||
path: '/custom-connectionline',
|
||||
component: CustomConnectionLine,
|
||||
},
|
||||
{
|
||||
path: '/custom-node',
|
||||
component: CustomNode,
|
||||
},
|
||||
{
|
||||
path: '/draghandle',
|
||||
component: DragHandle,
|
||||
},
|
||||
{
|
||||
path: '/dragndrop',
|
||||
component: DragNDrop,
|
||||
},
|
||||
{
|
||||
path: '/edges',
|
||||
component: Edges,
|
||||
},
|
||||
{
|
||||
path: '/edge-types',
|
||||
component: EdgeTypes,
|
||||
},
|
||||
{
|
||||
path: '/empty',
|
||||
component: Empty,
|
||||
},
|
||||
{
|
||||
path: '/floating-edges',
|
||||
component: FloatingEdges,
|
||||
},
|
||||
{
|
||||
path: '/hidden',
|
||||
component: Hidden,
|
||||
},
|
||||
{
|
||||
path: '/interaction',
|
||||
component: Interaction,
|
||||
},
|
||||
{
|
||||
path: '/layouting',
|
||||
component: Layouting,
|
||||
},
|
||||
{
|
||||
path: '/multiflows',
|
||||
component: MultiFlows,
|
||||
},
|
||||
{
|
||||
path: '/nested-nodes',
|
||||
component: NestedNodes,
|
||||
},
|
||||
{
|
||||
path: '/nodetype-change',
|
||||
component: NodeTypeChange,
|
||||
},
|
||||
{
|
||||
path: '/nodetypesobject-change',
|
||||
component: NodeTypesObjectChange,
|
||||
},
|
||||
{
|
||||
path: '/provider',
|
||||
component: Provider,
|
||||
},
|
||||
{
|
||||
path: '/save-restore',
|
||||
component: SaveRestore,
|
||||
},
|
||||
{
|
||||
path: '/stress',
|
||||
component: Stress,
|
||||
},
|
||||
{
|
||||
path: '/subflow',
|
||||
component: Subflow,
|
||||
},
|
||||
{
|
||||
path: '/switch',
|
||||
component: SwitchFlow,
|
||||
},
|
||||
{
|
||||
path: '/touch-device',
|
||||
component: TouchDevice,
|
||||
},
|
||||
{
|
||||
path: '/undirectional',
|
||||
component: Undirectional,
|
||||
},
|
||||
{
|
||||
path: '/updatable-edge',
|
||||
component: UpdatableEdge,
|
||||
},
|
||||
{
|
||||
path: '/update-node',
|
||||
component: UpdateNode,
|
||||
},
|
||||
{
|
||||
path: '/usereactflow',
|
||||
component: UseReactFlow,
|
||||
},
|
||||
{
|
||||
path: '/useupdatenodeinternals',
|
||||
component: UseUpdateNodeInternals,
|
||||
},
|
||||
{
|
||||
path: '/validation',
|
||||
component: Validation,
|
||||
},
|
||||
{
|
||||
path: '/controlled-uncontrolled',
|
||||
component: ControlledUncontrolled,
|
||||
},
|
||||
{
|
||||
path: '/use-key-press',
|
||||
component: UseKeyPress,
|
||||
},
|
||||
];
|
||||
|
||||
const Header = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const onChange = (event: ChangeEvent<HTMLSelectElement>) => navigate(event.target.value);
|
||||
|
||||
return (
|
||||
<header>
|
||||
<a className="logo" href="https://github.com/wbkd/react-flow">
|
||||
React Flow Dev
|
||||
</a>
|
||||
<select defaultValue={location.pathname} onChange={onChange}>
|
||||
{routes.map((route) => (
|
||||
<option value={route.path} key={route.path}>
|
||||
{route.path === '/' ? 'overview' : route.path.substring(1, route.path.length)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<Header />
|
||||
<Routes>
|
||||
{routes.map((route) => (
|
||||
<Route path={route.path} key={route.path} element={<route.component />} />
|
||||
))}
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
1
packages/core/example/src/react-app-env.d.ts
vendored
Normal file
1
packages/core/example/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
21
packages/core/example/tsconfig.json
Normal file
21
packages/core/example/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src_oldapi"]
|
||||
}
|
||||
6
packages/core/nocss/package.json
Normal file
6
packages/core/nocss/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"internal": true,
|
||||
"main": "../dist/nocss/umd/index.js",
|
||||
"module": "../dist/nocss/esm/index.js",
|
||||
"types": "../dist/nocss/esm/index.d.ts"
|
||||
}
|
||||
19771
packages/core/package-lock.json
generated
Normal file
19771
packages/core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
packages/core/package.json
Normal file
34
packages/core/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@react-flow/core",
|
||||
"version": "11.0.0",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"main": "dist/react-flow-core.cjs.js",
|
||||
"module": "dist/react-flow-core.esm.js",
|
||||
"sideEffects": false,
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wbkd/react-flow.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.0",
|
||||
"classcat": "^5.0.3",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16 || 17 || 18",
|
||||
"react-dom": "16 || 17 || 18"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"nocss"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.15"
|
||||
}
|
||||
}
|
||||
73
packages/core/src/additional-components/Background/index.tsx
Normal file
73
packages/core/src/additional-components/Background/index.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React, { memo, FC, useEffect, useState, useRef } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { createGridLinesPath, createGridDotsPath } from './utils';
|
||||
import { BackgroundVariant, ReactFlowState, BackgroundProps } from '../../types';
|
||||
|
||||
const defaultColors = {
|
||||
[BackgroundVariant.Dots]: '#81818a',
|
||||
[BackgroundVariant.Lines]: '#eee',
|
||||
};
|
||||
|
||||
const transformSelector = (s: ReactFlowState) => s.transform;
|
||||
|
||||
const Background: FC<BackgroundProps> = ({
|
||||
variant = BackgroundVariant.Dots,
|
||||
gap = 15,
|
||||
size = 0.4,
|
||||
color,
|
||||
style,
|
||||
className,
|
||||
}) => {
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
const [patternId, setPatternId] = useState<string | null>(null);
|
||||
const [tX, tY, tScale] = useStore(transformSelector);
|
||||
|
||||
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 scaledGap = gap * tScale || 1;
|
||||
const xOffset = tX % scaledGap;
|
||||
const yOffset = tY % scaledGap;
|
||||
|
||||
const isLines = variant === BackgroundVariant.Lines;
|
||||
const bgColor = color ? color : defaultColors[variant];
|
||||
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size * tScale, bgColor);
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={cc(['react-flow__background', 'react-flow__container', className])}
|
||||
style={{
|
||||
...style,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
Background.displayName = 'Background';
|
||||
|
||||
export default memo(Background);
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export const createGridLinesPath = (size: number, strokeWidth: number, stroke: string): React.ReactElement => {
|
||||
return <path stroke={stroke} strokeWidth={strokeWidth} d={`M${size / 2} 0 V${size} M0 ${size / 2} H${size}`} />;
|
||||
};
|
||||
|
||||
export const createGridDotsPath = (size: number, fill: string): React.ReactElement => {
|
||||
return <circle cx={size} cy={size} r={size} fill={fill} />;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function FitViewIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
|
||||
<path d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default FitViewIcon;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function LockIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 32">
|
||||
<path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default LockIcon;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function MinusIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 5">
|
||||
<path d="M0 0h32v4.2H0z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MinusIcon;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user