refactor(inactivemode): no pointer events for nodes and edges closes #218

This commit is contained in:
moklick
2020-05-13 11:09:22 +02:00
parent 3ce3472dc5
commit 1cc4e2676d
7 changed files with 59 additions and 48 deletions

View File

@@ -153,7 +153,40 @@ nodeTypes={{
You can now use type `special` for a node.
The `default`, `input` and `output` types will be still available except you overwrite one of them.
You can find an example of how to implement a custom node in the [custom node example](example/src/CustomNode).
You can find an example of how to implement a custom node in the [custom node example](/example/src/CustomNode).
## Handle
We export a `Handle` component as a helper for your custom nodes:
```javascript
import { Handle } from 'react-flow-renderer';
const targetHandleWithValidation = (
<Handle
type="target"
position="left"
isValidConnection={(connection) => connection.source === 'some-id'}
onConnect={params => console.log('handle onConnect', params)}
style={{ background: '#fff' }}
/>
);
```
### Props
- `type`: 'source' | 'target'
- `id`: string - you only need this when you have multiple source or target handles otherwise the node id is used
- `position`: 'left' | 'right' | 'top' | 'bottom' handle position - default: 'top' for type target, 'bottom' for type source
- `onConnect`: function that gets triggered on connect
- `isValidConnection`: function receives a connection `{ target: 'some-id', source: 'another-id' }` as param, returns a boolean - true by default
- `style`: css properties
### Multiple Handles
If you need multiple source our target handles you can achieve this by creating a custom node. Normally you just use the id of a node for the `source` or `target` of an edge. If you have multiple source or target handles you need to pass an id to these handles. These ids get then added to the node id, so that you can connect to a specific handle. If you have a node with an id = `1` and a handle with an id = `a` you can connect to this handle by using the id = `1__a`.
You can find an example of how to implement a custom node with multiple handles in the [custom node example](/example/src/CustomNode/ColorSelectorNode.js#L18-L29).
## Edges
@@ -281,7 +314,7 @@ const FlowWithControls = () => (
You can find all examples in the [example](example) folder or check out the live versions:
- [rich](https://react-flow.netlify.app/)
- [overview](https://react-flow.netlify.app/)
- [basic](https://react-flow.netlify.app/basic)
- [custom node](https://react-flow.netlify.app/custom-node)
- [horizontal](https://react-flow.netlify.app/horizontal)

View File

@@ -33,7 +33,7 @@ describe('Empty Flow Rendering', () => {
});
it('uses lock view control', () => {
cy.get('.react-flow__controls-interactive').click();
cy.get('.react-flow__controls-interactive');
});
it('renders an empty mini map', () => {

View File

@@ -9,11 +9,13 @@ describe('Inactive Graph Rendering', () => {
});
it('tries to select a node by click', () => {
cy.get('.react-flow__node:first').click().should('have.not.class', 'selected');
const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events');
expect(pointerEvents).to.equal('none');
});
it('tries to select an edge by click', () => {
cy.get('.react-flow__edge:first').click().should('have.not.class', 'selected');
const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events');
expect(pointerEvents).to.equal('none');
});
it('tries to do a selection', () => {
@@ -22,30 +24,6 @@ describe('Inactive Graph Rendering', () => {
cy.get('body').type('{shift}', { release: true });
});
it('tries to drag 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.equal(styleAfterDrag);
});
});
it('tries to connect nodes', () => {
cy.get('.react-flow__node')
.contains('Node 3')
.find('.react-flow__handle.source')
.trigger('mousedown', { which: 1 });
cy.get('.react-flow__node')
.contains('Node 4')
.find('.react-flow__handle.target')
.trigger('mousemove')
.trigger('mouseup', { force: true });
cy.get('.react-flow__edge').should('have.length', 2);
});
it('toggles interactive mode', () => {
cy.get('.react-flow__interactive').click();
});

View File

@@ -18,7 +18,7 @@ const initialElements = [
},
{
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">Github</a></> }
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: 300 } },
{ id: '6', type: 'output', data: { label: <>An <strong>output node</strong></> }, position: { x: 100, y: 450 } },

View File

@@ -37,6 +37,9 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
...rest
}: EdgeWrapperProps) => {
const edgeClasses = cx('react-flow__edge', { selected, animated });
const edgeGroupStyle: CSSProperties = {
pointerEvents: isInteractive ? 'all' : 'none',
};
const onEdgeClick = (): void => {
if (!isInteractive) {
return;
@@ -47,7 +50,7 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
};
return (
<g className={edgeClasses} onClick={onEdgeClick}>
<g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}>
<EdgeComponent
id={id}
source={source}

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState, memo, ComponentType } from 'react';
import React, { useEffect, useRef, useState, memo, ComponentType, CSSProperties } from 'react';
import { DraggableCore } from 'react-draggable';
import cx from 'classnames';
import { ResizeObserver } from 'resize-observer';
@@ -101,9 +101,10 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos };
const nodeClasses = cx('react-flow__node', { selected });
const nodeStyle = {
const nodeStyle: CSSProperties = {
zIndex: selected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: isInteractive ? 'all' : 'none',
};
const updateNode = (): void => {

View File

@@ -100,20 +100,6 @@
transform-origin: 0 0;
}
.is-interactive {
.react-flow__node {
cursor: grab;
&:hover > * {
box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);
}
}
.react-flow__handle {
cursor: crosshair;
}
}
.react-flow__node {
position: absolute;
color: #222;
@@ -123,10 +109,20 @@
user-select: none;
pointer-events: all;
transform-origin: 0 0;
cursor: grab;
&.selected > * {
&.selected > *,
&.selected:hover > * {
box-shadow: 0 0 0 2px #555;
}
&:hover > * {
box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08);
}
.react-flow__handle {
cursor: crosshair;
}
}
.react-flow__handle {