Merge pull request #208 from wbkd/develop

Text option for edges, release handling
This commit is contained in:
Christopher Möller
2020-05-11 13:18:23 +02:00
committed by GitHub
58 changed files with 2709 additions and 21772 deletions
+1 -1
View File
@@ -4,4 +4,4 @@ node_modules
examples/build
cypress/videos
cypress/screenshots
stats.html
dist
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
.cache
.vscode
.dependabot
node_modules
examples
cypress
dist
+13
View File
@@ -0,0 +1,13 @@
{
"git": {
"commitMessage": "chore: release v${version}"
},
"github": {
"release": true
},
"hooks": {
"before:init": ["npm test"],
"after:bump": "npm run build",
"after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
}
}
+9 -3
View File
@@ -31,7 +31,7 @@ React Flow is a library for building node-based graphs. You can easily implement
* **Plugin system:** [Mini map and graph controls](#plugins)
* **Reliable**: Written in [Typescript](https://www.typescriptlang.org/) and tested with [cypress](https://www.cypress.io/)
In order to make this library as flexible as possible we dont do any state updates besides the positions. This means that you need to pass all functions that change the displayed nodes and edges by yourself. You can implement your own ones or use the helper functions that come with the library.
In order to make this library as flexible as possible we dont do any state updates besides the positions. This means that you need to pass all functions that change the displayed nodes and edges by yourself. You can implement your own ones or use the helper functions that come with the library.
## Installation
@@ -130,7 +130,7 @@ You can find an example of how to implement a custom node in the [custom node ex
There are three [edge types](#edge-types--custom-edges) (`straight`, `default`, `step`) you can use. The default type is `default`. You can also create [custom edges](#edge-types--custom-edges).
Edge example: `{ id: 'e1-2', type: 'straight', source: '1', target: '2', animated: true }`
Edge example: `{ id: 'e1-2', type: 'straight', source: '1', target: '2', animated: true, label: 'edge label' }`
### Props
@@ -139,7 +139,13 @@ Edge example: `{ id: 'e1-2', type: 'straight', source: '1', target: '2', animate
- `target`: string (an id of a node) *(required)*
- `type`: 'input' | 'output' | 'default' or a custom one you implemented
- `animated`: boolean
- `style`: css properties
- `style`: css properties for the edge line path
- `label`: string
- `labelStyle`: css properties for the text
- `labelShowBg`: boolean - default: `true`
- `labelBgStyle`: css properties for the text background
You can find an example with lots of different edges in the [edges example](https://react-flow.netlify.app/edges).
### Edge Types / Custom Edges
-9518
View File
File diff suppressed because it is too large Load Diff
-1
View File
File diff suppressed because one or more lines are too long
-9532
View File
File diff suppressed because it is too large Load Diff
-1
View File
File diff suppressed because one or more lines are too long
-10
View File
@@ -1,10 +0,0 @@
import React, { HTMLAttributes } from 'react';
import { GridType } from '../../types';
interface GridProps extends HTMLAttributes<SVGElement> {
backgroundType?: GridType;
gap?: number;
color?: string;
size?: number;
}
declare const Grid: React.MemoExoticComponent<({ gap, color, size, style, className, backgroundType, }: GridProps) => JSX.Element>;
export default Grid;
-15
View File
@@ -1,15 +0,0 @@
import { SVGAttributes } from 'react';
import { ElementId, Node, Transform } from '../../types';
interface ConnectionLineProps {
connectionSourceId: ElementId;
connectionPositionX: number;
connectionPositionY: number;
connectionLineType?: string | null;
nodes: Node[];
transform: Transform;
isInteractive: boolean;
connectionLineStyle?: SVGAttributes<{}>;
className?: string;
}
declare const _default: ({ connectionSourceId, connectionLineStyle, connectionPositionX, connectionPositionY, connectionLineType, nodes, className, transform, isInteractive, }: ConnectionLineProps) => JSX.Element | null;
export default _default;
-4
View File
@@ -1,4 +0,0 @@
import React from 'react';
import { EdgeBezierProps } from '../../types';
declare const _default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, }: EdgeBezierProps) => JSX.Element>;
export default _default;
-4
View File
@@ -1,4 +0,0 @@
import React from 'react';
import { EdgeProps } from '../../types';
declare const _default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: EdgeProps) => JSX.Element>;
export default _default;
-4
View File
@@ -1,4 +0,0 @@
import React from 'react';
import { EdgeProps } from '../../types';
declare const _default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: EdgeProps) => JSX.Element>;
export default _default;
-14
View File
@@ -1,14 +0,0 @@
import React from 'react';
import { ElementId, Edge, EdgeCompProps } from '../../types';
interface EdgeWrapperProps {
id: ElementId;
source: ElementId;
target: ElementId;
type: any;
onClick: (edge: Edge) => void;
animated: boolean;
selected: boolean;
isInteractive: boolean;
}
declare const _default: (EdgeComponent: React.ComponentType<EdgeCompProps>) => React.MemoExoticComponent<({ id, source, target, type, animated, selected, onClick, isInteractive, ...rest }: EdgeWrapperProps) => JSX.Element>;
export default _default;
-17
View File
@@ -1,17 +0,0 @@
import React from 'react';
import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection } from '../../types';
declare type ValidConnectionFunc = (connection: Connection) => boolean;
declare type SetSourceIdFunc = (nodeId: ElementId | null) => void;
interface BaseHandleProps {
type: HandleType;
nodeId: ElementId;
onConnect: OnConnectFunc;
position: Position;
setSourceId: SetSourceIdFunc;
setPosition: (pos: XYPosition) => void;
isValidConnection: ValidConnectionFunc;
id?: ElementId | boolean;
className?: string;
}
declare const BaseHandle: React.MemoExoticComponent<({ type, nodeId, onConnect, position, setSourceId, setPosition, className, id, isValidConnection, ...rest }: BaseHandleProps) => JSX.Element>;
export default BaseHandle;
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { HandleType, Position, OnConnectFunc } from '../../types';
interface HandleProps {
type: HandleType;
position: Position;
onConnect?: OnConnectFunc;
isValidConnection?: () => boolean;
}
declare const Handle: React.MemoExoticComponent<({ onConnect, type, position, isValidConnection, ...rest }: HandleProps) => JSX.Element>;
export default Handle;
-4
View File
@@ -1,4 +0,0 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, targetPosition, sourcePosition, style }: NodeProps) => JSX.Element;
export default _default;
-4
View File
@@ -1,4 +0,0 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style, sourcePosition }: NodeProps) => JSX.Element;
export default _default;
-4
View File
@@ -1,4 +0,0 @@
/// <reference types="react" />
import { NodeProps } from '../../types';
declare const _default: ({ data, style, targetPosition }: NodeProps) => JSX.Element;
export default _default;
-2
View File
@@ -1,2 +0,0 @@
import { HandleElement } from '../../types';
export declare const getHandleBounds: (selector: string, nodeElement: HTMLDivElement, parentBounds: ClientRect | DOMRect, k: number) => HandleElement[] | null;
-4
View File
@@ -1,4 +0,0 @@
import React from 'react';
import { NodeComponentProps, WrapNodeProps } from '../../types';
declare const _default: (NodeComponent: React.ComponentType<NodeComponentProps>) => React.MemoExoticComponent<({ id, type, data, transform, xPos, yPos, selected, onClick, onNodeDragStop, style, isInteractive, sourcePosition, targetPosition, }: WrapNodeProps) => JSX.Element>;
export default _default;
-7
View File
@@ -1,7 +0,0 @@
/**
* The nodes selection rectangle gets displayed when a user
* made a selectio with on or several nodes
*/
import React from 'react';
declare const _default: React.MemoExoticComponent<() => JSX.Element>;
export default _default;
-9
View File
@@ -1,9 +0,0 @@
/**
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
*/
import React from 'react';
declare type UserSelectionProps = {
isInteractive: boolean;
};
declare const _default: React.MemoExoticComponent<({ isInteractive }: UserSelectionProps) => JSX.Element | null>;
export default _default;
-11
View File
@@ -1,11 +0,0 @@
import React, { SVGAttributes } from 'react';
interface EdgeRendererProps {
width: number;
height: number;
edgeTypes: any;
connectionLineStyle?: SVGAttributes<{}>;
connectionLineType?: string;
onElementClick?: () => void;
}
declare const EdgeRenderer: React.MemoExoticComponent<(props: EdgeRendererProps) => JSX.Element | null>;
export default EdgeRenderer;
-2
View File
@@ -1,2 +0,0 @@
import { EdgeTypesType } from '../../types';
export declare function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType;
-27
View File
@@ -1,27 +0,0 @@
import React, { SVGAttributes } from 'react';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
export interface GraphViewProps {
elements: Elements;
onElementClick: () => void;
onElementsRemove: (elements: Elements) => void;
onNodeDragStop: () => void;
onConnect: () => void;
onLoad: OnLoadFunc;
onMove: () => void;
selectionKeyCode: number;
nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType;
connectionLineType: string;
connectionLineStyle: SVGAttributes<{}>;
deleteKeyCode: number;
showBackground: boolean;
backgroundGap: number;
backgroundColor: string;
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
}
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, onlyRenderVisibleNodes, isInteractive, }: GraphViewProps) => JSX.Element>;
export default GraphView;
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { NodeTypesType } from '../../types';
interface NodeRendererProps {
nodeTypes: NodeTypesType;
onElementClick: () => void;
onNodeDragStop: () => void;
onlyRenderVisibleNodes?: boolean;
}
declare const NodeRenderer: React.MemoExoticComponent<({ onlyRenderVisibleNodes, ...props }: NodeRendererProps) => JSX.Element>;
export default NodeRenderer;
-2
View File
@@ -1,2 +0,0 @@
import { NodeTypesType } from '../../types';
export declare function createNodeTypes(nodeTypes: NodeTypesType): NodeTypesType;
-61
View File
@@ -1,61 +0,0 @@
import React, { SVGAttributes, HTMLAttributes } from 'react';
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
import '../../style.css';
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
elements: Elements;
onElementClick: () => void;
onElementsRemove: (elements: Elements) => void;
onNodeDragStop: () => void;
onConnect: () => void;
onLoad: OnLoadFunc;
onMove: () => void;
nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType;
connectionLineType: string;
connectionLineStyle: SVGAttributes<{}>;
deleteKeyCode: number;
selectionKeyCode: number;
showBackground: boolean;
backgroundGap: number;
backgroundColor: string;
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
}
declare const ReactFlow: {
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, onlyRenderVisibleNodes, isInteractive, }: ReactFlowProps): JSX.Element;
displayName: string;
defaultProps: {
onElementClick: () => void;
onElementsRemove: () => void;
onNodeDragStop: () => void;
onConnect: () => void;
onLoad: () => void;
onMove: () => void;
nodeTypes: {
input: ({ data, style, sourcePosition }: import("../../types").NodeProps) => JSX.Element;
default: ({ data, targetPosition, sourcePosition, style }: import("../../types").NodeProps) => JSX.Element;
output: ({ data, style, targetPosition }: import("../../types").NodeProps) => JSX.Element;
};
edgeTypes: {
default: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, }: import("../../types").EdgeBezierProps) => JSX.Element>;
straight: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: import("../../types").EdgeProps) => JSX.Element>;
step: React.MemoExoticComponent<({ sourceX, sourceY, targetX, targetY, style }: import("../../types").EdgeProps) => JSX.Element>;
};
connectionLineType: string;
connectionLineStyle: {};
deleteKeyCode: number;
selectionKeyCode: number;
backgroundColor: string;
backgroundGap: number;
showBackground: boolean;
backgroundType: GridType;
snapToGrid: boolean;
snapGrid: number[];
onlyRenderVisibleNodes: boolean;
isInteractive: boolean;
};
};
export default ReactFlow;
-7
View File
@@ -1,7 +0,0 @@
/// <reference types="react" />
import { ElementId } from '../types';
declare type ContextProps = ElementId | null;
export declare const NodeIdContext: import("react").Context<ContextProps>;
export declare const Provider: import("react").Provider<ContextProps>;
export declare const Consumer: import("react").Consumer<ContextProps>;
export default NodeIdContext;
-3
View File
@@ -1,3 +0,0 @@
import { MutableRefObject } from 'react';
declare const _default: (zoomPane: MutableRefObject<Element | null>, onMove: () => void, shiftPressed: boolean) => void;
export default _default;
-3
View File
@@ -1,3 +0,0 @@
import { Elements } from '../types';
declare const useElementUpdater: (elements: Elements) => void;
export default useElementUpdater;
-7
View File
@@ -1,7 +0,0 @@
import { Elements } from '../types';
interface HookParams {
deleteKeyCode: number;
onElementsRemove: (elements: Elements) => void;
}
declare const _default: ({ deleteKeyCode, onElementsRemove }: HookParams) => void;
export default _default;
-2
View File
@@ -1,2 +0,0 @@
declare const _default: (keyCode: number) => boolean;
export default _default;
-5
View File
@@ -1,5 +0,0 @@
import ReactFlow from './container/ReactFlow';
export default ReactFlow;
export { default as Handle } from './components/Handle';
export { MiniMap, Controls } from './plugins';
export { isNode, isEdge, removeElements, addEdge, getOutgoers, } from './utils/graph';
-5
View File
@@ -1,5 +0,0 @@
import React from 'react';
interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {
}
declare const _default: ({ style, className }: ControlProps) => JSX.Element;
export default _default;
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { Node } from '../../types';
declare type StringFunc = (node: Node) => string;
interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
nodeColor: string | StringFunc;
nodeBorderRadius: number;
maskColor: string;
}
declare const _default: ({ style, className, nodeColor, nodeBorderRadius, maskColor, }: MiniMapProps) => JSX.Element;
export default _default;
-2
View File
@@ -1,2 +0,0 @@
export { default as MiniMap } from './MiniMap';
export { default as Controls } from './Controls';
-71
View File
@@ -1,71 +0,0 @@
import { StoreModel } from './index';
export declare const useStoreActions: <Result>(mapActions: (actions: import("easy-peasy").ActionMapper<{
selectedNodesBbox: import("../types").Rect;
d3Zoom: import("d3-zoom").ZoomBehavior<Element, unknown> | null;
d3Selection: import("d3-selection").Selection<Element, unknown, null, undefined> | null;
selection: import("../types").SelectionRect | null;
userSelectionRect: import("../types").SelectionRect;
connectionPosition: import("../types").XYPosition;
onConnect: import("../types").OnConnectFunc;
setOnConnect: import("easy-peasy").Action<StoreModel, import("../types").OnConnectFunc>;
setNodes: import("easy-peasy").Action<StoreModel, import("../types").Node[]>;
setEdges: import("easy-peasy").Action<StoreModel, import("../types").Edge[]>;
updateNodeDimensions: import("easy-peasy").Action<StoreModel, {
id: string;
nodeElement: HTMLDivElement;
}>;
updateNodePos: import("easy-peasy").Action<StoreModel, {
id: string;
pos: import("../types").XYPosition;
}>;
setSelection: import("easy-peasy").Action<StoreModel, boolean>;
setNodesSelection: import("easy-peasy").Action<StoreModel, {
isActive: boolean;
selection?: import("../types").SelectionRect | undefined;
}>;
setSelectedElements: import("easy-peasy").Action<StoreModel, import("../types").Node | import("../types").Edge | import("../types").Elements>;
updateSelection: import("easy-peasy").Action<StoreModel, import("../types").SelectionRect>;
updateTransform: import("easy-peasy").Action<StoreModel, {
x: number;
y: number;
k: number;
}>;
updateSize: import("easy-peasy").Action<StoreModel, import("../types").Dimensions>;
initD3: import("easy-peasy").Action<StoreModel, {
zoom: import("d3-zoom").ZoomBehavior<Element, unknown>;
selection: import("d3-selection").Selection<Element, unknown, null, undefined>;
}>;
setSnapGrid: import("easy-peasy").Action<StoreModel, {
snapToGrid: boolean;
snapGrid: [number, number];
}>;
setConnectionPosition: import("easy-peasy").Action<StoreModel, import("../types").XYPosition>;
setConnectionSourceId: import("easy-peasy").Action<StoreModel, string | null>;
setInteractive: import("easy-peasy").Action<StoreModel, boolean>;
setUserSelection: import("easy-peasy").Action<StoreModel, import("../types").XYPosition>;
updateUserSelection: import("easy-peasy").Action<StoreModel, import("../types").XYPosition>;
unsetUserSelection: import("easy-peasy").Action<StoreModel, void>;
}, "1">) => Result) => Result;
export declare const useStoreDispatch: () => import("easy-peasy").Dispatch<StoreModel, import("redux").Action<any>>;
export declare const useStoreState: <Result>(mapState: (state: import("easy-peasy").StateMapper<{
width: number;
height: number;
transform: import("../types").Transform;
nodes: import("../types").Node[];
edges: import("../types").Edge[];
selectedElements: import("../types").Elements;
selectedNodesBbox: import("../types").Rect;
d3Zoom: import("d3-zoom").ZoomBehavior<Element, unknown> | null;
d3Selection: import("d3-selection").Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean;
nodesSelectionActive: boolean;
selectionActive: boolean;
selection: import("../types").SelectionRect | null;
userSelectionRect: import("../types").SelectionRect;
connectionSourceId: string | null;
connectionPosition: import("../types").XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: import("../types").OnConnectFunc;
}, "1">) => Result, equalityFn?: ((prev: Result, next: Result) => boolean) | undefined) => Result;
-71
View File
@@ -1,71 +0,0 @@
import { Action } from 'easy-peasy';
import { Selection as D3Selection, ZoomBehavior } from 'd3';
import { ElementId, Elements, Transform, Node, Edge, Rect, Dimensions, XYPosition, OnConnectFunc, SelectionRect } from '../types';
declare type TransformXYK = {
x: number;
y: number;
k: number;
};
declare type NodePosUpdate = {
id: ElementId;
pos: XYPosition;
};
declare type NodeDimensionUpdate = {
id: ElementId;
nodeElement: HTMLDivElement;
};
declare type SelectionUpdate = {
isActive: boolean;
selection?: SelectionRect;
};
declare type D3Init = {
zoom: ZoomBehavior<Element, unknown>;
selection: D3Selection<Element, unknown, null, undefined>;
};
declare type SetSnapGrid = {
snapToGrid: boolean;
snapGrid: [number, number];
};
export interface StoreModel {
width: number;
height: number;
transform: Transform;
nodes: Node[];
edges: Edge[];
selectedElements: Elements;
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
d3Initialised: boolean;
nodesSelectionActive: boolean;
selectionActive: boolean;
selection: SelectionRect | null;
userSelectionRect: SelectionRect;
connectionSourceId: ElementId | null;
connectionPosition: XYPosition;
snapToGrid: boolean;
snapGrid: [number, number];
isInteractive: boolean;
onConnect: OnConnectFunc;
setOnConnect: Action<StoreModel, OnConnectFunc>;
setNodes: Action<StoreModel, Node[]>;
setEdges: Action<StoreModel, Edge[]>;
updateNodeDimensions: Action<StoreModel, NodeDimensionUpdate>;
updateNodePos: Action<StoreModel, NodePosUpdate>;
setSelection: Action<StoreModel, boolean>;
setNodesSelection: Action<StoreModel, SelectionUpdate>;
setSelectedElements: Action<StoreModel, Elements | Node | Edge>;
updateSelection: Action<StoreModel, SelectionRect>;
updateTransform: Action<StoreModel, TransformXYK>;
updateSize: Action<StoreModel, Dimensions>;
initD3: Action<StoreModel, D3Init>;
setSnapGrid: Action<StoreModel, SetSnapGrid>;
setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, ElementId | null>;
setInteractive: Action<StoreModel, boolean>;
setUserSelection: Action<StoreModel, XYPosition>;
updateUserSelection: Action<StoreModel, XYPosition>;
unsetUserSelection: Action<StoreModel>;
}
declare const store: import("easy-peasy").Store<StoreModel, import("easy-peasy").EasyPeasyConfig<{}, any>>;
export default store;
-134
View File
@@ -1,134 +0,0 @@
import { CSSProperties, SVGAttributes } from 'react';
export declare type ElementId = string;
export declare type Elements = Array<Node | Edge>;
export declare type Transform = [number, number, number];
export declare enum Position {
Left = "left",
Top = "top",
Right = "right",
Bottom = "bottom"
}
export declare type XYPosition = {
x: number;
y: number;
};
export declare enum GridType {
Lines = "lines",
Dots = "dots"
}
export declare type HandleType = 'source' | 'target';
export declare type NodeTypesType = {
[key: string]: React.ReactNode;
};
export declare type EdgeTypesType = NodeTypesType;
export interface Dimensions {
width: number;
height: number;
}
export interface Rect extends Dimensions, XYPosition {
}
export interface Box extends XYPosition {
x2: number;
y2: number;
}
export interface SelectionRect extends Rect {
startX: number;
startY: number;
draw: boolean;
}
export interface Node {
id: ElementId;
position: XYPosition;
type?: string;
__rg?: any;
data?: any;
style?: CSSProperties;
targetPosition?: Position;
sourcePosition?: Position;
}
export interface Edge {
id: ElementId;
type?: string;
source: ElementId;
target: ElementId;
style?: SVGAttributes<{}>;
animated?: boolean;
}
export interface EdgeProps {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
style?: SVGAttributes<{}>;
}
export interface EdgeBezierProps extends EdgeProps {
sourcePosition: Position;
targetPosition: Position;
}
export interface NodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
targetPosition?: Position;
sourcePosition?: Position;
style?: CSSProperties;
}
export interface NodeComponentProps {
id: ElementId;
type: string;
data: any;
selected?: boolean;
transform?: Transform;
xPos?: number;
yPos?: number;
targetPosition?: Position;
sourcePosition?: Position;
onClick?: (node: Node) => void | undefined;
onNodeDragStop?: () => any;
style?: CSSProperties;
}
export interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
isInteractive: boolean;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
sourcePosition?: Position;
targetPosition?: Position;
}
export declare type FitViewParams = {
padding: number;
};
export declare type FitViewFunc = (fitViewOptions: FitViewParams) => void;
declare type OnLoadParams = {
zoomIn: () => void;
zoomOut: () => void;
fitView: FitViewFunc;
};
export declare type OnLoadFunc = (params: OnLoadParams) => void;
export declare type Connection = {
source: ElementId | null;
target: ElementId | null;
};
export declare type OnConnectFunc = (params: Connection) => void;
export interface HandleElement extends XYPosition, Dimensions {
id?: ElementId | null;
position: Position;
}
export interface EdgeCompProps {
id: ElementId;
source: ElementId;
target: ElementId;
type: any;
onClick?: (edge: Edge) => void;
animated?: boolean;
selected?: boolean;
}
export {};
-15
View File
@@ -1,15 +0,0 @@
import { Node, Edge, Elements, Transform, XYPosition, Rect, FitViewParams } from '../types';
export declare const isEdge: (element: Node | Edge) => boolean;
export declare const isNode: (element: Node | Edge) => boolean;
export declare const getOutgoers: (node: Node, elements: Elements) => Elements;
export declare const removeElements: (elementsToRemove: Elements, elements: Elements) => Elements;
export declare const addEdge: (edgeParams: Edge, elements: Elements) => Elements;
export declare const parseElement: (element: Node | Edge, transform: Transform, snapToGrid: boolean, snapGrid: [number, number]) => Node | Edge;
export declare const getBoundsofRects: (rect1: Rect, rect2: Rect) => Rect;
export declare const getRectOfNodes: (nodes: Node[]) => Rect;
export declare const graphPosToZoomedPos: ({ x, y }: XYPosition, [tx, ty, tScale]: Transform) => XYPosition;
export declare const getNodesInside: (nodes: Node[], rect: Rect, [tx, ty, tScale]?: Transform, partially?: boolean) => Node[];
export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[];
export declare const fitView: ({ padding }?: FitViewParams) => void;
export declare const zoomIn: () => void;
export declare const zoomOut: () => void;
-6
View File
@@ -1,6 +0,0 @@
import { MouseEvent as ReactMouseEvent } from 'react';
export declare const isInputDOMNode: (e: MouseEvent | ReactMouseEvent<Element, MouseEvent> | ReactMouseEvent<HTMLElement | SVGElement, MouseEvent> | import("react").TouchEvent<HTMLElement | SVGElement> | TouchEvent | KeyboardEvent) => boolean;
export declare const getDimensions: (node: HTMLDivElement) => {
width: number;
height: number;
};
+1 -1
View File
@@ -11,7 +11,7 @@ const initialElements = [
{ 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 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-2', source: '1', target: '2', animated: true, label: 'edge text' },
{ id: 'e1-3', source: '1', target: '3' },
];
+51
View File
@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import ReactFlow, { removeElements, addEdge, MiniMap, Controls } from 'react-flow-renderer';
const onNodeDragStop = node => console.log('drag stop', node);
const onElementClick = element => console.log('click', element);
const onLoad = (graph) => {
graph.fitView();
};
const initialElements = [
{ id: '1', type: 'input', data: { label: 'Input Node 1' }, position: { x: 250, y: 0 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 250, y: 200 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } },
{ id: '5', data: { label: 'Node 5' }, position: { x: 450, y: 400 } },
{ id: '6', type: 'output', data: { label: 'Output Node 5' }, position: { x: 250, y: 550 } },
{ id: '7', type: 'output', data: { label: 'Output Node 6' }, position: { x: 550, y: 550 } },
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge', labelBgStyle: { fillOpacity: 0.75 } },
{ 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-5', source: '4', target: '5', animated: true, label: 'animated styled edge', style: { stroke: 'red' } },
{ id: 'e5-6', source: '5', target: '6', label: 'styled label', labelStyle: { fill: 'red', fontWeight: 700 } },
{ id: 'e5-7', source: '5', target: '7', label: 'label with styled bg', labelBgStyle: { fill: '#eee', fillOpacity: 0.7 } },
]
const EdgesFlow = () => {
const [elements, setElements] = useState(initialElements);
const onElementsRemove = (elementsToRemove) => setElements(els => removeElements(elementsToRemove, els));
const onConnect = (params) => setElements(els => addEdge(params, els));
return (
<ReactFlow
elements={elements}
onElementClick={onElementClick}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
style={{ width: '100%', height: '100%' }}
onLoad={onLoad}
snapToGrid={true}
>
<MiniMap />
<Controls />
</ReactFlow>
);
}
export default EdgesFlow;
+3 -3
View File
@@ -18,13 +18,13 @@ const initialElements = [
sourcePosition: 'right'
},
{ id: '4', data: { label: 'Node 4' }, position: { x: 500, y: 200 }, targetPosition: 'left' },
{ id: '5', type: 'output', data: { label: 'Output Node 5'}, position: { x: 300, y: 300 } },
{ id: '5', type: 'output', data: { label: 'Output Node 5' }, position: { x: 300, y: 300 } },
{ id: '6', type: 'output', data: { label: 'Output Node 6' }, position: { x: 600, y: 400 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-2', source: '1', target: '2', animated: true, label: 'edge text', labelBgStyle: { fillOpacity: 0.75 } },
{ id: 'e2-3', source: '2', target: '3', animated: true },
{ id: 'e3-4', source: '3', target: '4', animated: true },
{ id: 'e3-5', source: '4', target: '5', animated: true, type: 'step' },
{ id: 'e5-6b', source: '4', target: '6', type: 'step' },
{ id: 'e5-6b', source: '4', target: '6', type: 'step', label: 'styled label', labelStyle: { fill: 'red', fontWeight: 700 } },
]
const RichGraph = () => {
+6 -1
View File
@@ -8,6 +8,7 @@ import CustomNode from './CustomNode';
import Stress from './Stress';
import Inactive from './Inactive';
import Empty from './Empty';
import Edges from './Edges';
import './index.css';
@@ -27,10 +28,14 @@ const routes = [{
path: '/stress',
component: Stress,
label: 'Stress'
}, {
path: '/edges',
component: Edges,
label: 'Edges'
}, {
path: '/empty',
component: Empty
},{
}, {
path: '/inactive',
component: Inactive
}];
+2366 -2063
View File
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -10,7 +10,7 @@
"url": "https://github.com/wbkd/react-flow.git"
},
"dependencies": {
"@welldone-software/why-did-you-render": "^4.1.3",
"@welldone-software/why-did-you-render": "^4.2.0",
"classnames": "^2.2.6",
"d3-selection": "^1.4.1",
"d3-zoom": "^1.8.3",
@@ -20,14 +20,14 @@
"resize-observer": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/core": "^7.9.6",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"@svgr/rollup": "^5.4.0",
"@types/classnames": "^2.2.10",
"@types/d3": "^5.7.2",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.6",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"autoprefixer": "^9.7.6",
"babel-loader": "^8.1.0",
"babel-preset-react-app": "^9.1.2",
@@ -36,11 +36,12 @@
"prettier": "2.0.5",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"rollup": "^2.7.6",
"release-it": "^13.5.8",
"rollup": "^2.9.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-livereload": "^1.1.0",
"rollup-plugin-livereload": "^1.3.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-peer-deps-external": "^2.2.2",
"rollup-plugin-postcss": "^3.1.1",
@@ -49,10 +50,9 @@
"rollup-plugin-terser": "^5.3.0",
"rollup-plugin-typescript2": "^0.27.0",
"rollup-plugin-uglify": "^6.0.4",
"rollup-plugin-visualizer": "^4.0.4",
"start-server-and-test": "^1.11.0",
"typescript": "^3.8.3",
"wait-on": "^4.0.2"
"wait-on": "^5.0.0"
},
"peerDependencies": {
"react": "^16.13.1"
@@ -60,13 +60,15 @@
"scripts": {
"build": "rollup -c --environment NODE_ENV:production",
"start": "rollup -w -c",
"dev": "npm start & cd example && npm start",
"build:example": "npm install && cd example && npm install && npm run build",
"waiton:dist": "wait-on dist/ReactFlow.js",
"dev": "npm start & npm run waiton:dist && cd example && npm start",
"dev:wait": "start-server-and-test dev http-get://localhost:3000",
"build:example": "npm install && npm run build && cd example && npm install && npm run build",
"cy:run": "cypress run",
"cy:open": "cypress open",
"cypress": "npm run dev:wait cy:open",
"test": "npm run dev:wait cy:run"
"test": "npm run dev:wait cy:run",
"release": "release-it"
},
"files": [
"dist"
+44 -41
View File
@@ -3,52 +3,55 @@ import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import postcss from 'rollup-plugin-postcss';
import bundleSize from 'rollup-plugin-bundle-size';
import visualizer from 'rollup-plugin-visualizer';
import replace from 'rollup-plugin-replace';
import svgr from '@svgr/rollup';
import typescript from 'rollup-plugin-typescript2'
import typescript from 'rollup-plugin-typescript2';
import pkg from './package.json';
const isProd = process.env.NODE_ENV === 'production';
const processEnv = isProd ? 'production' : 'development';
export default [{
input: 'src/index.ts',
external: ['react', 'react-dom'],
onwarn(warning, rollupWarn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY') {
rollupWarn(warning);
}
},
output: [{
file: pkg.main,
format: 'cjs',
sourcemap: true,
exports: 'named'
}, {
file: pkg.module,
format: 'esm',
sourcemap: true,
exports: 'named'
}],
plugins: [
bundleSize(),
postcss(),
babel({
exclude: 'node_modules/**'
}),
visualizer(),
replace({
'process.env.NODE_ENV': JSON.stringify(processEnv)
}),
svgr(),
typescript({
clean: true
}),
resolve(),
commonjs({
include: 'node_modules/**'
}),
]
}];
export default [
{
input: 'src/index.ts',
external: ['react', 'react-dom'],
onwarn(warning, rollupWarn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY') {
rollupWarn(warning);
}
},
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: true,
exports: 'named',
},
{
file: pkg.module,
format: 'esm',
sourcemap: true,
exports: 'named',
},
],
plugins: [
bundleSize(),
postcss(),
babel({
exclude: 'node_modules/**',
}),
replace({
'process.env.NODE_ENV': JSON.stringify(processEnv),
}),
svgr(),
typescript({
clean: true,
}),
resolve(),
commonjs({
include: 'node_modules/**',
}),
],
},
];
+25 -4
View File
@@ -1,5 +1,6 @@
import React, { memo } from 'react';
import EdgeText from './EdgeText';
import { EdgeBezierProps, Position } from '../../types';
export default memo(
@@ -10,23 +11,43 @@ export default memo(
targetY,
sourcePosition = Position.Bottom,
targetPosition = Position.Top,
label,
labelStyle,
labelShowBg,
labelBgStyle,
style = {},
}: EdgeBezierProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
let dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
const leftAndRight = [Position.Left, Position.Right];
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
dAttr = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`;
} else if (leftAndRight.includes(sourcePosition) || leftAndRight.includes(targetPosition)) {
dAttr = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`;
}
return <path {...style} d={dAttr} />;
const text = label ? (
<EdgeText
x={centerX}
y={centerY}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
/>
) : null;
return (
<g>
<path style={style} d={dAttr} className="react-flow__edge-path" />
{text}
</g>
);
}
);
+44
View File
@@ -0,0 +1,44 @@
import React, { memo, useRef, useState, useEffect } from 'react';
import { EdgeTextProps, Rect } from '../../types';
const textBgPadding = 2;
export default memo(({ x, y, label, labelStyle = {}, labelShowBg = true, labelBgStyle = {} }: EdgeTextProps) => {
const edgeRef = useRef<SVGTextElement>(null);
const [edgeTextBbox, setEdgeTextBbox] = useState<Rect>({ x: 0, y: 0, width: 0, height: 0 });
useEffect(() => {
if (edgeRef.current) {
const textBbox = edgeRef.current.getBBox();
setEdgeTextBbox({
x: textBbox.x,
y: textBbox.y,
width: textBbox.width,
height: textBbox.height,
});
}
}, []);
if (typeof label === 'undefined' || !label) {
return null;
}
return (
<g transform={`translate(${x - edgeTextBbox.width / 2} ${y - edgeTextBbox.height / 2})`}>
{labelShowBg && (
<rect
width={edgeTextBbox.width + 2 * textBgPadding}
x={-textBgPadding}
y={-textBgPadding}
height={edgeTextBbox.height + 2 * textBgPadding}
className="react-flow__edge-textbg"
style={labelBgStyle}
/>
)}
<text className="react-flow__edge-text" y={edgeTextBbox.height / 2} dy="0.3em" ref={edgeRef} style={labelStyle}>
{label}
</text>
</g>
);
});
+24 -5
View File
@@ -1,17 +1,36 @@
import React, { memo } from 'react';
import EdgeText from './EdgeText';
import { EdgeProps } from '../../types';
export default memo(
({ sourceX, sourceY, targetX, targetY, style = {} }: EdgeProps) => {
({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, style = {} }: EdgeProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
return (
<path
{...style}
d={`M ${sourceX},${sourceY}L ${sourceX},${centerY}L ${targetX},${centerY}L ${targetX},${targetY}`}
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
const text = label ? (
<EdgeText
x={centerX}
y={centerY}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
/>
) : null;
return (
<g>
<path
style={style}
className="react-flow__edge-path"
d={`M ${sourceX},${sourceY}L ${sourceX},${centerY}L ${targetX},${centerY}L ${targetX},${targetY}`}
/>
{text}
</g>
);
}
);
+23 -2
View File
@@ -1,11 +1,32 @@
import React, { memo } from 'react';
import EdgeText from './EdgeText';
import { EdgeProps } from '../../types';
export default memo(
({ sourceX, sourceY, targetX, targetY, style = {} }: EdgeProps) => {
({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, style = {} }: EdgeProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
const xOffset = Math.abs(targetX - sourceX) / 2;
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
const text = label ? (
<EdgeText
x={centerX}
y={centerY}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
/>
) : null;
return (
<path {...style} d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} />
<g>
<path style={style} className="react-flow__edge-path" d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} />
{text}
</g>
);
}
);
+24 -2
View File
@@ -1,4 +1,4 @@
import React, { memo, ComponentType } from 'react';
import React, { memo, ComponentType, CSSProperties } from 'react';
import cx from 'classnames';
import store from '../../store';
@@ -9,6 +9,10 @@ interface EdgeWrapperProps {
source: ElementId;
target: ElementId;
type: any;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle: CSSProperties;
onClick: (edge: Edge) => void;
animated: boolean;
selected: boolean;
@@ -17,7 +21,21 @@ interface EdgeWrapperProps {
export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
const EdgeWrapper = memo(
({ id, source, target, type, animated, selected, onClick, isInteractive, ...rest }: EdgeWrapperProps) => {
({
id,
source,
target,
type,
animated,
selected,
onClick,
isInteractive,
label,
labelStyle,
labelShowBg,
labelBgStyle,
...rest
}: EdgeWrapperProps) => {
const edgeClasses = cx('react-flow__edge', { selected, animated });
const onEdgeClick = (): void => {
if (!isInteractive) {
@@ -38,6 +56,10 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
animated={animated}
selected={selected}
onClick={onClick}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
{...rest}
/>
</g>
+9 -5
View File
@@ -83,7 +83,7 @@ function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleE
if (bounds.length === 1 || !handleId) {
handle = bounds[0];
} else if (handleId) {
handle = bounds.find(d => d.id === handleId);
handle = bounds.find((d) => d.id === handleId);
}
return handle;
@@ -123,8 +123,8 @@ function renderEdge(
const [sourceId, sourceHandleId] = edge.source.split('__');
const [targetId, targetHandleId] = edge.target.split('__');
const sourceNode = nodes.find(n => n.id === sourceId);
const targetNode = nodes.find(n => n.id === targetId);
const sourceNode = nodes.find((n) => n.id === sourceId);
const targetNode = nodes.find((n) => n.id === targetId);
if (!sourceNode) {
throw new Error(`couldn't create edge for source id: ${sourceId}`);
@@ -155,7 +155,7 @@ function renderEdge(
);
const isSelected = (selectedElements as Edge[]).some(
elm => isEdge(elm) && elm.source === sourceId && elm.target === targetId
(elm) => isEdge(elm) && elm.source === sourceId && elm.target === targetId
);
return (
@@ -166,6 +166,10 @@ function renderEdge(
onClick={props.onElementClick}
selected={isSelected}
animated={edge.animated}
label={edge.label}
labelStyle={edge.labelStyle}
labelShowBg={edge.labelShowBg}
labelBgStyle={edge.labelBgStyle}
style={edge.style}
source={sourceId}
target={targetId}
@@ -191,7 +195,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
connectionPosition: { x, y },
selectedElements,
isInteractive,
} = useStoreState(s => ({
} = useStoreState((s) => ({
transform: s.transform,
edges: s.edges,
nodes: s.nodes,
+20 -8
View File
@@ -46,16 +46,9 @@
}
.react-flow__edge {
fill: none;
stroke: #bbb;
stroke-width: 2;
pointer-events: all;
&.selected {
stroke: #555;
}
&.animated {
&.animated path {
stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite;
}
@@ -66,6 +59,25 @@
}
}
.react-flow__edge-path {
fill: none;
stroke: #bbb;
stroke-width: 2;
&.selected {
stroke: #555;
}
}
.react-flow__edge-text {
font-size: 12px;
pointer-events: none;
}
.react-flow__edge-textbg {
fill: white;
}
@keyframes dashdraw {
from {
stroke-dashoffset: 10;
+24 -3
View File
@@ -1,4 +1,4 @@
import { CSSProperties, SVGAttributes } from 'react';
import { CSSProperties } from 'react';
export type ElementId = string;
@@ -63,7 +63,11 @@ export interface Edge {
type?: string;
source: ElementId;
target: ElementId;
style?: SVGAttributes<{}>;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
style?: CSSProperties;
animated?: boolean;
}
@@ -72,7 +76,11 @@ export interface EdgeProps {
sourceY: number;
targetX: number;
targetY: number;
style?: SVGAttributes<{}>;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
style?: CSSProperties;
}
export interface EdgeBezierProps extends EdgeProps {
@@ -151,7 +159,20 @@ export interface EdgeCompProps {
source: ElementId;
target: ElementId;
type: any;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
onClick?: (edge: Edge) => void;
animated?: boolean;
selected?: boolean;
}
export interface EdgeTextProps {
x: number;
y: number;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
}