diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..dcdc02e1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +website/* linguist-documentation +example/* linguist-documentation diff --git a/README.md b/README.md index 53f8bb05..2ddb7d73 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ - ![react-flow](https://user-images.githubusercontent.com/2857535/88070904-7e5e2c80-cb73-11ea-9f69-6b8647eef24a.png) +![react-flow](https://user-images.githubusercontent.com/2857535/95224198-b2540000-07fa-11eb-832d-361d72d60345.jpg) + +## ✨ React Flow has a [new website](https://reactflow.dev) ✨ # React Flow @@ -130,6 +132,12 @@ const BasicFlow = () => ; - `deleteKeyCode`: default: `8` (delete) - `selectionKeyCode`: default: `16` (shift) +**Typescript:** The interface of the ReactFlow Prop Types are exported as `ReactFlowProps`. You can use it in your code as follows: + +```javascript +import { ReactFlowProps } from 'react-flow-renderer'; +``` + ## React Flow Instance You can receive a `reactFlowInstance` by using the `onLoad` callback: @@ -271,6 +279,8 @@ const targetHandleWithValidation = ( - `style`: css properties - `className`: additional class name +**Typescript:** The interface of the Handle Prop Types are exported as `HandleProps`. + ### Validation The handle receives the additional class names `connecting` when the connection line is above the handle and `valid` if the connection is valid. You can find an example which uses these classes [here](/example/src/Validation/index.js). @@ -412,6 +422,8 @@ const FlowWithBackground = () => ( - `style`: css properties - `className`: additional class name +**Typescript:** The interface of the Background Prop Types are exported as `BackgroundProps`. + ## MiniMap You can use the mini map plugin by passing it as a children to the `ReactFlow` component: @@ -445,6 +457,8 @@ const FlowWithMiniMap = () => ( - `style`: css properties - `className`: additional class name +**Typescript:** The interface of the MiniMap Prop Types are exported as `MiniMapProps`. + ## Controls The control panel contains a zoom-in, zoom-out, fit-view and a lock/unlock button. You can use it by passing it as a children to the `ReactFlow` component: @@ -471,6 +485,8 @@ const FlowWithControls = () => ( - `style`: css properties - `className`: additional class name +**Typescript:** The interface of the Controls Prop Types are exported as `ControlProps`. + ## ReactFlowProvider If you need access to the internal state and action of React Flow outside of the `ReactFlow` component you can wrap it with the `ReactFlowProvider` component: @@ -510,8 +526,8 @@ The React Flow wrapper has the className `react-flow`. If you want to change the ### Used Class Names * `.react-flow` - Outer container -* `.react-flow__renderer` - Inner container -* `.react-flow__zoompane` - Zoom & pan pane +* `.react-flow__renderer` - Flow container, used for zooming and panning +* `.react-flow__pane` - Background Pane for click and wheel events, previously `.react-flow__zoompane` * `.react-flow__selectionpane` - Selection pane * `.react-flow__selection` - User selection * `.react-flow__edges` - Edges wrapper diff --git a/cypress/integration/flow/basic.spec.js b/cypress/integration/flow/basic.spec.js index 27e27b56..2d39054f 100644 --- a/cypress/integration/flow/basic.spec.js +++ b/cypress/integration/flow/basic.spec.js @@ -119,7 +119,7 @@ describe('Basic Flow Rendering', () => { // for d3 we have to pass the window to the event // https://github.com/cypress-io/cypress/issues/3441 cy.window().then((win) => { - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('mousedown', 'topLeft', { which: 1, view: win }) .trigger('mousemove', 'bottomLeft') .trigger('mouseup', { force: true, view: win }) @@ -133,7 +133,7 @@ describe('Basic Flow Rendering', () => { it('zooms the pane', () => { const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform'); - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('wheel', 'topLeft', { deltaY: -200 }) .then(() => { const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform'); diff --git a/cypress/integration/flow/controls.spec.js b/cypress/integration/flow/controls.spec.js index 3975fadb..98e0def9 100644 --- a/cypress/integration/flow/controls.spec.js +++ b/cypress/integration/flow/controls.spec.js @@ -36,7 +36,7 @@ describe('Controls Testing', () => { // for d3 we have to pass the window to the event // https://github.com/cypress-io/cypress/issues/3441 cy.window().then((win) => { - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('mousedown', 'topLeft', { which: 1, view: win }) .trigger('mousemove', 'bottomLeft') .trigger('mouseup', { force: true, view: win }) diff --git a/cypress/integration/flow/empty.spec.js b/cypress/integration/flow/empty.spec.js index 300dc850..52c32b16 100644 --- a/cypress/integration/flow/empty.spec.js +++ b/cypress/integration/flow/empty.spec.js @@ -16,6 +16,8 @@ describe('Empty Flow Rendering', () => { .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('renders an empty mini map', () => { diff --git a/cypress/integration/flow/interaction.spec.js b/cypress/integration/flow/interaction.spec.js index 8846aade..5810c485 100644 --- a/cypress/integration/flow/interaction.spec.js +++ b/cypress/integration/flow/interaction.spec.js @@ -52,7 +52,7 @@ describe('Interaction Flow Rendering', () => { it('tries to zoom by scroll', () => { const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform'); - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .dblclick() .then(() => { const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform'); @@ -63,7 +63,7 @@ describe('Interaction Flow Rendering', () => { it('tries to zoom by double click', () => { const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform'); - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('wheel', 'topLeft', { deltaY: -200 }) .then(() => { const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform'); @@ -122,7 +122,7 @@ describe('Interaction Flow Rendering', () => { it('zooms by scroll', () => { const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform'); - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('wheel', 'topLeft', { deltaY: 200 }) .then(() => { const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform'); @@ -137,7 +137,7 @@ describe('Interaction Flow Rendering', () => { it('zooms by double click', () => { const styleBeforeZoom = Cypress.$('.react-flow__nodes').css('transform'); - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .dblclick() .then(() => { const styleAfterZoom = Cypress.$('.react-flow__nodes').css('transform'); diff --git a/cypress/integration/flow/minimap.spec.js b/cypress/integration/flow/minimap.spec.js index 1ce5bb5a..c9235ea8 100644 --- a/cypress/integration/flow/minimap.spec.js +++ b/cypress/integration/flow/minimap.spec.js @@ -19,7 +19,7 @@ describe('Minimap Testing', () => { const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap').attr('viewBox'); const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d'); - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('wheel', 'topLeft', { deltaY: -200 }) .then(() => { const viewBoxAfterZoom = Cypress.$('.react-flow__minimap').attr('viewBox'); @@ -53,7 +53,7 @@ describe('Minimap Testing', () => { // for d3 we have to pass the window to the event // https://github.com/cypress-io/cypress/issues/3441 cy.window().then((win) => { - cy.get('.react-flow__zoompane') + cy.get('.react-flow__renderer') .trigger('mousedown', 'topLeft', { which: 1, view: win }) .trigger('mousemove', 'bottomLeft') .trigger('mouseup', { force: true, view: win }) diff --git a/example/package-lock.json b/example/package-lock.json index 631169fb..eda3f447 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -1,5 +1,5 @@ { - "name": "react-flow-examples", + "name": "react-flow-tests", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -3996,11 +3996,6 @@ "type": "^1.0.1" } }, - "d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - }, "damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", @@ -10458,10 +10453,7 @@ "react-flow-renderer": { "version": "file:..", "requires": { - "@welldone-software/why-did-you-render": "^4.3.2", "classcat": "^4.1.0", - "d3-selection": "^2.0.0", - "d3-zoom": "^2.0.0", "easy-peasy": "^3.3.1", "fast-deep-equal": "^3.1.3", "react-draggable": "^4.4.3", @@ -16087,8 +16079,7 @@ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", "requires": { - "d3-dispatch": "1", - "d3-selection": "1" + "d3-dispatch": "1" } }, "d3-ease": { @@ -16118,7 +16109,6 @@ "d3-dispatch": "1", "d3-ease": "1", "d3-interpolate": "1", - "d3-selection": "^1.1.0", "d3-timer": "1" } }, @@ -16130,7 +16120,6 @@ "d3-dispatch": "1", "d3-drag": "1", "d3-interpolate": "1", - "d3-selection": "1", "d3-transition": "1" } }, diff --git a/example/package.json b/example/package.json index da6cd466..3cfbcea4 100644 --- a/example/package.json +++ b/example/package.json @@ -1,5 +1,5 @@ { - "name": "react-flow-examples", + "name": "react-flow-tests", "version": "0.1.0", "private": true, "dependencies": { diff --git a/example/public/favicon.ico b/example/public/favicon.ico index c2c86b85..731acd59 100644 Binary files a/example/public/favicon.ico and b/example/public/favicon.ico differ diff --git a/example/public/index.html b/example/public/index.html index 6e06a78f..f49c16b5 100644 --- a/example/public/index.html +++ b/example/public/index.html @@ -4,12 +4,10 @@ - - - React Flow Examples + + React Flow - Test Flows -
diff --git a/example/public/logo192.png b/example/public/logo192.png deleted file mode 100644 index fa313abf..00000000 Binary files a/example/public/logo192.png and /dev/null differ diff --git a/example/public/logo512.png b/example/public/logo512.png deleted file mode 100644 index bd5d4b5e..00000000 Binary files a/example/public/logo512.png and /dev/null differ diff --git a/example/public/manifest.json b/example/public/manifest.json deleted file mode 100644 index 080d6c77..00000000 --- a/example/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/example/src/Horizontal/index.js b/example/src/Horizontal/index.js deleted file mode 100644 index 3b9ce9fb..00000000 --- a/example/src/Horizontal/index.js +++ /dev/null @@ -1,90 +0,0 @@ -import React, { useState } from 'react'; - -import ReactFlow, { removeElements, addEdge } from 'react-flow-renderer'; - -const onLoad = (reactFlowInstance) => reactFlowInstance.fitView(); - -const onNodeMouseEnter = (event, node) => console.log('mouse enter:', node); -const onNodeMouseMove = (event, node) => console.log('mouse move:', node); -const onNodeMouseLeave = (event, node) => console.log('mouse leave:', node); -const onNodeContextMenu = (event, node) => { - event.preventDefault(); - console.log('context menu:', node); -}; - -const initialElements = [ - { - id: '1', - sourcePosition: 'right', - type: 'input', - className: 'dark-node', - data: { label: 'Input' }, - position: { x: 0, y: 80 }, - }, - { id: '2', sourcePosition: 'right', targetPosition: 'left', data: { label: 'A Node' }, position: { x: 250, y: 0 } }, - { id: '3', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 3' }, position: { x: 250, y: 160 } }, - { id: '4', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 4' }, position: { x: 500, y: 0 } }, - { id: '5', sourcePosition: 'top', targetPosition: 'bottom', data: { label: 'Node 5' }, position: { x: 500, y: 100 } }, - { id: '6', sourcePosition: 'bottom', targetPosition: 'top', data: { label: 'Node 6' }, position: { x: 500, y: 230 } }, - { - id: '7', - type: 'output', - sourcePosition: 'right', - targetPosition: 'left', - data: { label: 'Node 7' }, - position: { x: 750, y: 50 }, - }, - { - id: '8', - type: 'output', - sourcePosition: 'right', - targetPosition: 'left', - data: { label: 'Node 8' }, - position: { x: 750, y: 300 }, - }, - - { id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true }, - { id: 'e1-3', source: '1', type: 'smoothstep', target: '3', animated: true }, - { id: 'e1-4', source: '2', type: 'smoothstep', target: '4', label: 'edge label' }, - { id: 'e3-5', source: '3', type: 'smoothstep', target: '5', animated: true }, - { id: 'e3-6', source: '3', type: 'smoothstep', target: '6', animated: true }, - { id: 'e5-7', source: '5', type: 'smoothstep', target: '7', animated: true }, - { id: 'e6-8', source: '6', type: 'smoothstep', target: '8', animated: true }, -]; - -const HorizontalFlow = () => { - const [elements, setElements] = useState(initialElements); - const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els)); - const onConnect = (params) => setElements((els) => addEdge(params, els)); - const changeClassName = () => { - setElements((elms) => - elms.map((el) => { - if (el.type === 'input') { - el.className = el.className ? '' : 'dark-node'; - } - - return { ...el }; - }) - ); - }; - - return ( - - - - ); -}; - -export default HorizontalFlow; diff --git a/example/src/Overview/index.js b/example/src/Overview/index.js index d5703ead..1f1f230d 100644 --- a/example/src/Overview/index.js +++ b/example/src/Overview/index.js @@ -4,6 +4,9 @@ import ReactFlow, { removeElements, addEdge, MiniMap, Controls, Background, isNo const onNodeDragStart = (event, node) => console.log('drag start', node); const onNodeDragStop = (event, node) => console.log('drag stop', node); +const onPaneClick = (event) => console.log('pane click', event); +const onPaneScroll = (event) => console.log('pane scroll', event); +const onPaneContextMenu = (event) => console.log('pane context menu', event); const onSelectionDrag = (event, nodes) => console.log('selection drag', nodes); const onSelectionDragStart = (event, nodes) => console.log('selection drag start', nodes); const onSelectionDragStop = (event, nodes) => console.log('selection drag stop', nodes); @@ -125,6 +128,9 @@ const OverviewFlow = () => { onElementClick={onElementClick} onElementsRemove={onElementsRemove} onConnect={onConnect} + onPaneClick={onPaneClick} + onPaneScroll={onPaneScroll} + onPaneContextMenu={onPaneContextMenu} onNodeDragStart={onNodeDragStart} onNodeDragStop={onNodeDragStop} onSelectionDragStart={onSelectionDragStart} diff --git a/example/src/index.css b/example/src/index.css index 8ad19a95..76f50147 100644 --- a/example/src/index.css +++ b/example/src/index.css @@ -1,7 +1,5 @@ -@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); - body { - font-family: 'Roboto', sans-serif; + font-family: sans-serif; color: #111; } diff --git a/example/src/index.js b/example/src/index.js index 0a4473e9..cd470fbe 100644 --- a/example/src/index.js +++ b/example/src/index.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import ReactDOM from 'react-dom'; -import { BrowserRouter as Router, Route, Switch, NavLink, withRouter } from 'react-router-dom'; +import { BrowserRouter as Router, Route, Switch, NavLink } from 'react-router-dom'; import Overview from './Overview'; import Basic from './Basic'; @@ -10,7 +10,6 @@ import Interaction from './Interaction'; import Empty from './Empty'; import Edges from './Edges'; import Validation from './Validation'; -import Horizontal from './Horizontal'; import Provider from './Provider'; import Hidden from './Hidden'; import EdgeTypes from './EdgeTypes'; @@ -36,11 +35,6 @@ const routes = [ component: CustomNode, label: 'CustomNode', }, - { - path: '/horizontal', - component: Horizontal, - label: 'Horizontal', - }, { path: '/validation', component: Validation, @@ -94,24 +88,13 @@ const routes = [ const navLinks = routes.filter((route) => route.label); -const SourceDisplay = withRouter(({ location }) => { - const route = routes.find((route) => route.path === location.pathname); - const sourceLink = `https://github.com/wbkd/react-flow/tree/main/example/src/${route.label}/index.js`; - - return ( - - {''} - - ); -}); - const Header = () => { const [menuOpen, setMenuOpen] = useState(); return (
- React Flow + React Flow Dev