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 examples/build
cypress/videos cypress/videos
cypress/screenshots 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}."
}
}
+8 -2
View File
@@ -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). 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 ### 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)* - `target`: string (an id of a node) *(required)*
- `type`: 'input' | 'output' | 'default' or a custom one you implemented - `type`: 'input' | 'output' | 'default' or a custom one you implemented
- `animated`: boolean - `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 ### 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: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, 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: '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' }, { 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' sourcePosition: 'right'
}, },
{ id: '4', data: { label: 'Node 4' }, position: { x: 500, y: 200 }, targetPosition: 'left' }, { 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: '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: 'e2-3', source: '2', target: '3', animated: true },
{ id: 'e3-4', source: '3', target: '4', animated: true }, { id: 'e3-4', source: '3', target: '4', animated: true },
{ id: 'e3-5', source: '4', target: '5', animated: true, type: 'step' }, { 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 = () => { const RichGraph = () => {
+6 -1
View File
@@ -8,6 +8,7 @@ import CustomNode from './CustomNode';
import Stress from './Stress'; import Stress from './Stress';
import Inactive from './Inactive'; import Inactive from './Inactive';
import Empty from './Empty'; import Empty from './Empty';
import Edges from './Edges';
import './index.css'; import './index.css';
@@ -27,10 +28,14 @@ const routes = [{
path: '/stress', path: '/stress',
component: Stress, component: Stress,
label: 'Stress' label: 'Stress'
}, {
path: '/edges',
component: Edges,
label: 'Edges'
}, { }, {
path: '/empty', path: '/empty',
component: Empty component: Empty
},{ }, {
path: '/inactive', path: '/inactive',
component: 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" "url": "https://github.com/wbkd/react-flow.git"
}, },
"dependencies": { "dependencies": {
"@welldone-software/why-did-you-render": "^4.1.3", "@welldone-software/why-did-you-render": "^4.2.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"d3-selection": "^1.4.1", "d3-selection": "^1.4.1",
"d3-zoom": "^1.8.3", "d3-zoom": "^1.8.3",
@@ -20,14 +20,14 @@
"resize-observer": "^1.0.0" "resize-observer": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.9.6",
"@babel/preset-env": "^7.9.0", "@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4", "@babel/preset-react": "^7.9.4",
"@svgr/rollup": "^5.4.0", "@svgr/rollup": "^5.4.0",
"@types/classnames": "^2.2.10", "@types/classnames": "^2.2.10",
"@types/d3": "^5.7.2", "@types/d3": "^5.7.2",
"@types/react": "^16.9.34", "@types/react": "^16.9.35",
"@types/react-dom": "^16.9.6", "@types/react-dom": "^16.9.8",
"autoprefixer": "^9.7.6", "autoprefixer": "^9.7.6",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"babel-preset-react-app": "^9.1.2", "babel-preset-react-app": "^9.1.2",
@@ -36,11 +36,12 @@
"prettier": "2.0.5", "prettier": "2.0.5",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.13.1", "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-babel": "^4.4.0",
"rollup-plugin-bundle-size": "^1.0.3", "rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-commonjs": "^10.1.0", "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-node-resolve": "^5.2.0",
"rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-peer-deps-external": "^2.2.2",
"rollup-plugin-postcss": "^3.1.1", "rollup-plugin-postcss": "^3.1.1",
@@ -49,10 +50,9 @@
"rollup-plugin-terser": "^5.3.0", "rollup-plugin-terser": "^5.3.0",
"rollup-plugin-typescript2": "^0.27.0", "rollup-plugin-typescript2": "^0.27.0",
"rollup-plugin-uglify": "^6.0.4", "rollup-plugin-uglify": "^6.0.4",
"rollup-plugin-visualizer": "^4.0.4",
"start-server-and-test": "^1.11.0", "start-server-and-test": "^1.11.0",
"typescript": "^3.8.3", "typescript": "^3.8.3",
"wait-on": "^4.0.2" "wait-on": "^5.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.13.1" "react": "^16.13.1"
@@ -60,13 +60,15 @@
"scripts": { "scripts": {
"build": "rollup -c --environment NODE_ENV:production", "build": "rollup -c --environment NODE_ENV:production",
"start": "rollup -w -c", "start": "rollup -w -c",
"dev": "npm start & cd example && npm start", "waiton:dist": "wait-on dist/ReactFlow.js",
"build:example": "npm install && cd example && npm install && npm run build", "dev": "npm start & npm run waiton:dist && cd example && npm start",
"dev:wait": "start-server-and-test dev http-get://localhost:3000", "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:run": "cypress run",
"cy:open": "cypress open", "cy:open": "cypress open",
"cypress": "npm run dev:wait cy:open", "cypress": "npm run dev:wait cy:open",
"test": "npm run dev:wait cy:run" "test": "npm run dev:wait cy:run",
"release": "release-it"
}, },
"files": [ "files": [
"dist" "dist"
+44 -41
View File
@@ -3,52 +3,55 @@ import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel'; import babel from 'rollup-plugin-babel';
import postcss from 'rollup-plugin-postcss'; import postcss from 'rollup-plugin-postcss';
import bundleSize from 'rollup-plugin-bundle-size'; import bundleSize from 'rollup-plugin-bundle-size';
import visualizer from 'rollup-plugin-visualizer';
import replace from 'rollup-plugin-replace'; import replace from 'rollup-plugin-replace';
import svgr from '@svgr/rollup'; import svgr from '@svgr/rollup';
import typescript from 'rollup-plugin-typescript2' import typescript from 'rollup-plugin-typescript2';
import pkg from './package.json'; import pkg from './package.json';
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
const processEnv = isProd ? 'production' : 'development'; const processEnv = isProd ? 'production' : 'development';
export default [{ export default [
input: 'src/index.ts', {
external: ['react', 'react-dom'], input: 'src/index.ts',
onwarn(warning, rollupWarn) { external: ['react', 'react-dom'],
if (warning.code !== 'CIRCULAR_DEPENDENCY') { onwarn(warning, rollupWarn) {
rollupWarn(warning); if (warning.code !== 'CIRCULAR_DEPENDENCY') {
} rollupWarn(warning);
}, }
output: [{ },
file: pkg.main, output: [
format: 'cjs', {
sourcemap: true, file: pkg.main,
exports: 'named' format: 'cjs',
}, { sourcemap: true,
file: pkg.module, exports: 'named',
format: 'esm', },
sourcemap: true, {
exports: 'named' file: pkg.module,
}], format: 'esm',
plugins: [ sourcemap: true,
bundleSize(), exports: 'named',
postcss(), },
babel({ ],
exclude: 'node_modules/**' plugins: [
}), bundleSize(),
visualizer(), postcss(),
replace({ babel({
'process.env.NODE_ENV': JSON.stringify(processEnv) exclude: 'node_modules/**',
}), }),
svgr(), replace({
typescript({ 'process.env.NODE_ENV': JSON.stringify(processEnv),
clean: true }),
}), svgr(),
resolve(), typescript({
commonjs({ clean: true,
include: 'node_modules/**' }),
}), resolve(),
] commonjs({
}]; include: 'node_modules/**',
}),
],
},
];
+25 -4
View File
@@ -1,5 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import EdgeText from './EdgeText';
import { EdgeBezierProps, Position } from '../../types'; import { EdgeBezierProps, Position } from '../../types';
export default memo( export default memo(
@@ -10,23 +11,43 @@ export default memo(
targetY, targetY,
sourcePosition = Position.Bottom, sourcePosition = Position.Bottom,
targetPosition = Position.Top, targetPosition = Position.Top,
label,
labelStyle,
labelShowBg,
labelBgStyle,
style = {}, style = {},
}: EdgeBezierProps) => { }: EdgeBezierProps) => {
const yOffset = Math.abs(targetY - sourceY) / 2; const yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
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}`; let dAttr = `M${sourceX},${sourceY} C${sourceX},${centerY} ${targetX},${centerY} ${targetX},${targetY}`;
const leftAndRight = [Position.Left, Position.Right]; const leftAndRight = [Position.Left, Position.Right];
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { 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}`; dAttr = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`;
} else if (leftAndRight.includes(sourcePosition) || leftAndRight.includes(targetPosition)) { } else if (leftAndRight.includes(sourcePosition) || leftAndRight.includes(targetPosition)) {
dAttr = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`; 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 React, { memo } from 'react';
import EdgeText from './EdgeText';
import { EdgeProps } from '../../types'; import { EdgeProps } from '../../types';
export default memo( 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 yOffset = Math.abs(targetY - sourceY) / 2;
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
return ( const xOffset = Math.abs(targetX - sourceX) / 2;
<path const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
{...style}
d={`M ${sourceX},${sourceY}L ${sourceX},${centerY}L ${targetX},${centerY}L ${targetX},${targetY}`} 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 React, { memo } from 'react';
import EdgeText from './EdgeText';
import { EdgeProps } from '../../types'; import { EdgeProps } from '../../types';
export default memo( 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 ( 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 cx from 'classnames';
import store from '../../store'; import store from '../../store';
@@ -9,6 +9,10 @@ interface EdgeWrapperProps {
source: ElementId; source: ElementId;
target: ElementId; target: ElementId;
type: any; type: any;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle: CSSProperties;
onClick: (edge: Edge) => void; onClick: (edge: Edge) => void;
animated: boolean; animated: boolean;
selected: boolean; selected: boolean;
@@ -17,7 +21,21 @@ interface EdgeWrapperProps {
export default (EdgeComponent: ComponentType<EdgeCompProps>) => { export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
const EdgeWrapper = memo( 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 edgeClasses = cx('react-flow__edge', { selected, animated });
const onEdgeClick = (): void => { const onEdgeClick = (): void => {
if (!isInteractive) { if (!isInteractive) {
@@ -38,6 +56,10 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
animated={animated} animated={animated}
selected={selected} selected={selected}
onClick={onClick} onClick={onClick}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
{...rest} {...rest}
/> />
</g> </g>
+9 -5
View File
@@ -83,7 +83,7 @@ function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleE
if (bounds.length === 1 || !handleId) { if (bounds.length === 1 || !handleId) {
handle = bounds[0]; handle = bounds[0];
} else if (handleId) { } else if (handleId) {
handle = bounds.find(d => d.id === handleId); handle = bounds.find((d) => d.id === handleId);
} }
return handle; return handle;
@@ -123,8 +123,8 @@ function renderEdge(
const [sourceId, sourceHandleId] = edge.source.split('__'); const [sourceId, sourceHandleId] = edge.source.split('__');
const [targetId, targetHandleId] = edge.target.split('__'); const [targetId, targetHandleId] = edge.target.split('__');
const sourceNode = nodes.find(n => n.id === sourceId); const sourceNode = nodes.find((n) => n.id === sourceId);
const targetNode = nodes.find(n => n.id === targetId); const targetNode = nodes.find((n) => n.id === targetId);
if (!sourceNode) { if (!sourceNode) {
throw new Error(`couldn't create edge for source id: ${sourceId}`); throw new Error(`couldn't create edge for source id: ${sourceId}`);
@@ -155,7 +155,7 @@ function renderEdge(
); );
const isSelected = (selectedElements as Edge[]).some( 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 ( return (
@@ -166,6 +166,10 @@ function renderEdge(
onClick={props.onElementClick} onClick={props.onElementClick}
selected={isSelected} selected={isSelected}
animated={edge.animated} animated={edge.animated}
label={edge.label}
labelStyle={edge.labelStyle}
labelShowBg={edge.labelShowBg}
labelBgStyle={edge.labelBgStyle}
style={edge.style} style={edge.style}
source={sourceId} source={sourceId}
target={targetId} target={targetId}
@@ -191,7 +195,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
connectionPosition: { x, y }, connectionPosition: { x, y },
selectedElements, selectedElements,
isInteractive, isInteractive,
} = useStoreState(s => ({ } = useStoreState((s) => ({
transform: s.transform, transform: s.transform,
edges: s.edges, edges: s.edges,
nodes: s.nodes, nodes: s.nodes,
+20 -8
View File
@@ -46,16 +46,9 @@
} }
.react-flow__edge { .react-flow__edge {
fill: none;
stroke: #bbb;
stroke-width: 2;
pointer-events: all; pointer-events: all;
&.selected { &.animated path {
stroke: #555;
}
&.animated {
stroke-dasharray: 5; stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite; 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 { @keyframes dashdraw {
from { from {
stroke-dashoffset: 10; stroke-dashoffset: 10;
+24 -3
View File
@@ -1,4 +1,4 @@
import { CSSProperties, SVGAttributes } from 'react'; import { CSSProperties } from 'react';
export type ElementId = string; export type ElementId = string;
@@ -63,7 +63,11 @@ export interface Edge {
type?: string; type?: string;
source: ElementId; source: ElementId;
target: ElementId; target: ElementId;
style?: SVGAttributes<{}>; label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
style?: CSSProperties;
animated?: boolean; animated?: boolean;
} }
@@ -72,7 +76,11 @@ export interface EdgeProps {
sourceY: number; sourceY: number;
targetX: number; targetX: number;
targetY: number; targetY: number;
style?: SVGAttributes<{}>; label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
style?: CSSProperties;
} }
export interface EdgeBezierProps extends EdgeProps { export interface EdgeBezierProps extends EdgeProps {
@@ -151,7 +159,20 @@ export interface EdgeCompProps {
source: ElementId; source: ElementId;
target: ElementId; target: ElementId;
type: any; type: any;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
onClick?: (edge: Edge) => void; onClick?: (edge: Edge) => void;
animated?: boolean; animated?: boolean;
selected?: boolean; selected?: boolean;
} }
export interface EdgeTextProps {
x: number;
y: number;
label?: string;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
}