From f2313916b6a09c28b8a0b616f936f81a4433a236 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 29 May 2020 16:50:59 +0200 Subject: [PATCH 1/4] feat(handle): add class name prop type --- README.md | 1 + src/components/Handle/index.tsx | 8 ++++++-- src/types/index.ts | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5d0815a0..bd6cb6ab 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,7 @@ const targetHandleWithValidation = ( - `onConnect`: function that gets triggered on connect - `isValidConnection`: function receives a connection `{ target: 'some-id', source: 'another-id' }` as param, returns a boolean - true by default - `style`: css properties +- `className`: additional class name #### Multiple Handles diff --git a/src/components/Handle/index.tsx b/src/components/Handle/index.tsx index 44d686d5..56a544c0 100644 --- a/src/components/Handle/index.tsx +++ b/src/components/Handle/index.tsx @@ -12,7 +12,9 @@ const Handle = memo( position = Position.Top, onConnect = () => {}, isValidConnection = () => true, - ...rest + style, + className, + id, }: HandleProps) => { const nodeId = useContext(NodeIdContext) as ElementId; const setPosition = useStoreActions((a) => a.setConnectionPosition); @@ -32,7 +34,9 @@ const Handle = memo( type={type} position={position} isValidConnection={isValidConnection} - {...rest} + style={style} + className={className} + id={id} /> ); } diff --git a/src/types/index.ts b/src/types/index.ts index 34b84d42..b45da6e4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -168,6 +168,7 @@ export interface HandleProps { isValidConnection?: (connection: Connection) => boolean; id?: string; style?: CSSProperties; + className?: string; } export interface EdgeCompProps { From 714c9161212c777a2e678abf2f2f21337e36ae28 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 29 May 2020 18:33:29 +0200 Subject: [PATCH 2/4] refactor(styling): use css for default styles (#259) * refactor(minimap): use css for default styles * refactor(controls): use css for default styles * refactor(background): use css for default styles * refactor(edges): add edge type class name * refactor(nodes): use css for default styles * refactor(outputnode): add display name * refactor(wrapnode): cleanup * refactor(rollup): minimize css for prod build * refactor(additional-comps): extract css * test(graphs): adjust for new markup --- assets/icons/unlock.svg | 2 +- cypress/integration/flow/basic.spec.js | 2 +- cypress/integration/flow/inactive.spec.js | 2 +- example/src/CustomNode/ColorSelectorNode.js | 8 +-- example/src/CustomNode/index.js | 2 +- example/src/index.css | 6 +- rollup.config.js | 4 +- .../Background/index.tsx | 14 ++--- .../Background/style.css | 5 ++ src/additional-components/Controls/index.tsx | 19 ++---- src/additional-components/Controls/style.css | 28 +++++++++ .../MiniMap/MiniMapNode.tsx | 1 + src/additional-components/MiniMap/index.tsx | 28 +++------ src/additional-components/MiniMap/style.css | 6 ++ src/components/Edges/BezierEdge.tsx | 2 +- src/components/Edges/StepEdge.tsx | 2 +- src/components/Edges/StraightEdge.tsx | 2 +- src/components/Edges/wrapEdge.tsx | 6 +- src/components/Nodes/DefaultNode.tsx | 19 +++--- src/components/Nodes/InputNode.tsx | 19 +++--- src/components/Nodes/OutputNode.tsx | 19 +++--- src/components/Nodes/wrapNode.tsx | 16 ++--- src/style.css | 61 ++++++++----------- src/types/index.ts | 1 - 24 files changed, 131 insertions(+), 143 deletions(-) create mode 100644 src/additional-components/Background/style.css create mode 100644 src/additional-components/Controls/style.css create mode 100644 src/additional-components/MiniMap/style.css diff --git a/assets/icons/unlock.svg b/assets/icons/unlock.svg index 22ed8018..ba34f03f 100644 --- a/assets/icons/unlock.svg +++ b/assets/icons/unlock.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/cypress/integration/flow/basic.spec.js b/cypress/integration/flow/basic.spec.js index dccabd28..e160f369 100644 --- a/cypress/integration/flow/basic.spec.js +++ b/cypress/integration/flow/basic.spec.js @@ -6,7 +6,7 @@ describe('Basic Graph Rendering', () => { cy.get('.react-flow-basic-example'); // check if className prop works cy.get('.react-flow__node').should('have.length', 4); cy.get('.react-flow__edge').should('have.length', 2); - cy.get('.react-flow__node').children('div').children('.react-flow__handle'); + cy.get('.react-flow__node').children('.react-flow__handle'); }); it('renders a grid', () => { diff --git a/cypress/integration/flow/inactive.spec.js b/cypress/integration/flow/inactive.spec.js index 44209137..751318f3 100644 --- a/cypress/integration/flow/inactive.spec.js +++ b/cypress/integration/flow/inactive.spec.js @@ -5,7 +5,7 @@ describe('Inactive Graph Rendering', () => { cy.get('.react-flow__renderer'); cy.get('.react-flow__node').should('have.length', 4); cy.get('.react-flow__edge').should('have.length', 2); - cy.get('.react-flow__node').children('div').children('.react-flow__handle'); + cy.get('.react-flow__node').children('.react-flow__handle'); }); it('tries to select a node by click', () => { diff --git a/example/src/CustomNode/ColorSelectorNode.js b/example/src/CustomNode/ColorSelectorNode.js index 53b687e0..fa5f2447 100644 --- a/example/src/CustomNode/ColorSelectorNode.js +++ b/example/src/CustomNode/ColorSelectorNode.js @@ -2,11 +2,9 @@ import React from 'react'; import { Handle } from 'react-flow-renderer'; -export default ({ data, styles }) => { +export default ({ data }) => { return ( -
+ <> { id="b" style={{ bottom: 10, top: 'auto', background: '#fff' }} /> -
+ ); }; diff --git a/example/src/CustomNode/index.js b/example/src/CustomNode/index.js index 96f7eda7..b64793e0 100644 --- a/example/src/CustomNode/index.js +++ b/example/src/CustomNode/index.js @@ -37,7 +37,7 @@ const CustomNodeFlow = () => { setElements([ { id: '1', type: 'input', data: { label: 'An input node' }, position: { x: 0, y: 50 }, sourcePosition: 'right' }, - { id: '2', type: 'selectorNode', data: { onChange: onChange, color: initBgColor }, position: { x: 250, y: 50 } }, + { id: '2', type: 'selectorNode', data: { onChange: onChange, color: initBgColor }, style: { border: '1px solid #777' }, position: { x: 250, y: 50 } }, { id: '3', type: 'output', data: { label: 'Output A' }, position: { x: 550, y: 25 }, targetPosition: 'left' }, { id: '4', type: 'output', data: { label: 'Output B' }, position: { x: 550, y: 100 }, targetPosition: 'left' }, diff --git a/example/src/index.css b/example/src/index.css index cf5db29f..1851ded3 100644 --- a/example/src/index.css +++ b/example/src/index.css @@ -136,9 +136,9 @@ nav a.active:before { color: #111; } -.dark-node>div { - background: #333 !important; - color: #f8f8f8 !important; +.dark-node { + background: #333; + color: #f8f8f8; } @media screen and (min-width: 768px) { diff --git a/rollup.config.js b/rollup.config.js index ddd31efb..32e8c51f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -37,7 +37,9 @@ export default [ ], plugins: [ bundleSize(), - postcss(), + postcss({ + minimize: isProd, + }), babel({ exclude: 'node_modules/**', }), diff --git a/src/additional-components/Background/index.tsx b/src/additional-components/Background/index.tsx index 782dd1b4..4228a089 100644 --- a/src/additional-components/Background/index.tsx +++ b/src/additional-components/Background/index.tsx @@ -1,10 +1,12 @@ -import React, { memo, HTMLAttributes, CSSProperties } from 'react'; +import React, { memo, HTMLAttributes } from 'react'; import classnames from 'classnames'; import { useStoreState } from '../../store/hooks'; import { BackgroundVariant } from '../../types'; import { createGridLinesPath, createGridDotsPath } from './utils'; +import './style.css'; + interface BackgroundProps extends HTMLAttributes { variant?: BackgroundVariant; gap?: number; @@ -12,19 +14,13 @@ interface BackgroundProps extends HTMLAttributes { size?: number; } -const baseStyles: CSSProperties = { - position: 'absolute', - top: 0, - left: 0, -}; - const defaultColors = { [BackgroundVariant.Dots]: '#999', [BackgroundVariant.Lines]: '#eee', }; const Background = memo( - ({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style = {}, className = '' }: BackgroundProps) => { + ({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style, className }: BackgroundProps) => { const width = useStoreState((s) => s.width); const height = useStoreState((s) => s.height); const [x, y, scale] = useStoreState((s) => s.transform); @@ -42,7 +38,7 @@ const Background = memo( const stroke = isLines ? bgColor : 'none'; return ( - + ); diff --git a/src/additional-components/Background/style.css b/src/additional-components/Background/style.css new file mode 100644 index 00000000..b96234b9 --- /dev/null +++ b/src/additional-components/Background/style.css @@ -0,0 +1,5 @@ +.react-flow__background { + position: absolute; + top: 0; + left: 0; +} \ No newline at end of file diff --git a/src/additional-components/Controls/index.tsx b/src/additional-components/Controls/index.tsx index 44bf2f7f..6c110bd8 100644 --- a/src/additional-components/Controls/index.tsx +++ b/src/additional-components/Controls/index.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties } from 'react'; +import React from 'react'; import classnames from 'classnames'; import { fitView, zoomIn, zoomOut } from '../../utils/graph'; @@ -10,12 +10,7 @@ import FitviewIcon from '../../../assets/icons/fitview.svg'; import LockIcon from '../../../assets/icons/lock.svg'; import UnlockIcon from '../../../assets/icons/unlock.svg'; -const baseStyle: CSSProperties = { - position: 'absolute', - zIndex: 5, - bottom: 10, - left: 10, -}; +import './style.css'; interface ControlProps extends React.HTMLAttributes { showZoom?: boolean; @@ -26,16 +21,10 @@ interface ControlProps extends React.HTMLAttributes { const Controls = ({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => { const setInteractive = useStoreActions((actions) => actions.setInteractive); const isInteractive = useStoreState((s) => s.isInteractive); - const mapClasses: string = classnames('react-flow__controls', className); + const mapClasses = classnames('react-flow__controls', className); return ( -
+
{showZoom && ( <>
diff --git a/src/additional-components/Controls/style.css b/src/additional-components/Controls/style.css new file mode 100644 index 00000000..eb70bfac --- /dev/null +++ b/src/additional-components/Controls/style.css @@ -0,0 +1,28 @@ +.react-flow__controls { + position: absolute; + z-index: 5; + bottom: 10px; + left: 10px; + box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08); + + &-button { + background: #fefefe; + border-bottom: 1px solid #eee; + display: flex; + justify-content: center; + align-items: center; + width: 16px; + height: 16px; + cursor: pointer; + user-select: none; + padding: 5px; + + svg { + width: 100%; + } + + &:hover { + background: #f4f4f4; + } + } +} \ No newline at end of file diff --git a/src/additional-components/MiniMap/MiniMapNode.tsx b/src/additional-components/MiniMap/MiniMapNode.tsx index 02ff5e2f..3f316df9 100644 --- a/src/additional-components/MiniMap/MiniMapNode.tsx +++ b/src/additional-components/MiniMap/MiniMapNode.tsx @@ -16,6 +16,7 @@ const MiniMapNode = ({ node, color, borderRadius }: MiniMapNodeProps) => { } = node.__rg; const { background, backgroundColor } = node.style || {}; const fill = (background || backgroundColor || color) as string; + return ( string; interface MiniMapProps extends React.HTMLAttributes { @@ -14,14 +16,8 @@ interface MiniMapProps extends React.HTMLAttributes { maskColor?: string; } -const baseStyle: CSSProperties = { - position: 'absolute', - zIndex: 5, - bottom: 10, - right: 10, - width: 200, - height: 150, -}; +const defaultWidth = 200; +const defaultHeight = 150; const MiniMap = ({ style = { backgroundColor: '#f8f8f8' }, @@ -36,11 +32,10 @@ const MiniMap = ({ const nodes = useStoreState((s) => s.nodes); const mapClasses = classnames('react-flow__minimap', className); - const elementWidth = (style.width || baseStyle.width)! as number; - const elementHeight = (style.height || baseStyle.height)! as number; + const elementWidth = (style.width || defaultWidth)! as number; + const elementHeight = (style.height || defaultHeight)! as number; const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc; const hasNodes = nodes && nodes.length; - const bb = getRectOfNodes(nodes); const viewBB: Rect = { x: -tX / tScale, @@ -48,17 +43,13 @@ const MiniMap = ({ width: containerWidth / tScale, height: containerHeight / tScale, }; - const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB; - const scaledWidth = boundingRect.width / elementWidth; const scaledHeight = boundingRect.height / elementHeight; const viewScale = Math.max(scaledWidth, scaledHeight); const viewWidth = viewScale * elementWidth; const viewHeight = viewScale * elementHeight; - const offset = 5 * viewScale; - const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; const width = viewWidth + offset * 2; @@ -69,10 +60,7 @@ const MiniMap = ({ width={elementWidth} height={elementHeight} viewBox={`${x} ${y} ${width} ${height}`} - style={{ - ...baseStyle, - ...style, - }} + style={style} className={mapClasses} > {nodes.map((node) => ( diff --git a/src/additional-components/MiniMap/style.css b/src/additional-components/MiniMap/style.css new file mode 100644 index 00000000..f7f566e5 --- /dev/null +++ b/src/additional-components/MiniMap/style.css @@ -0,0 +1,6 @@ +.react-flow__minimap { + position: absolute; + z-index: 5; + bottom: 10px; + right: 10px; +} \ No newline at end of file diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index 5486408e..b349c868 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -15,7 +15,7 @@ export default memo( labelStyle, labelShowBg, labelBgStyle, - style = {}, + style, }: EdgeBezierProps) => { const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; diff --git a/src/components/Edges/StepEdge.tsx b/src/components/Edges/StepEdge.tsx index 485d8e6b..e3d8621f 100644 --- a/src/components/Edges/StepEdge.tsx +++ b/src/components/Edges/StepEdge.tsx @@ -4,7 +4,7 @@ import EdgeText from './EdgeText'; import { EdgeProps } from '../../types'; export default memo( - ({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, 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; diff --git a/src/components/Edges/StraightEdge.tsx b/src/components/Edges/StraightEdge.tsx index 878bcba2..13b62620 100644 --- a/src/components/Edges/StraightEdge.tsx +++ b/src/components/Edges/StraightEdge.tsx @@ -4,7 +4,7 @@ import EdgeText from './EdgeText'; import { EdgeProps } from '../../types'; export default memo( - ({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, 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; diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx index bf27894f..df6d1158 100644 --- a/src/components/Edges/wrapEdge.tsx +++ b/src/components/Edges/wrapEdge.tsx @@ -13,8 +13,9 @@ interface EdgeWrapperProps { labelStyle?: CSSProperties; labelShowBg?: boolean; labelBgStyle: CSSProperties; + className?: string; onClick?: (edge: Edge) => void; - animated: boolean; + animated?: boolean; selected: boolean; isInteractive: boolean; } @@ -34,9 +35,10 @@ export default (EdgeComponent: ComponentType) => { labelStyle, labelShowBg, labelBgStyle, + className, ...rest }: EdgeWrapperProps) => { - const edgeClasses = cx('react-flow__edge', { selected, animated }); + const edgeClasses = cx('react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }); const edgeGroupStyle: CSSProperties = { pointerEvents: isInteractive ? 'all' : 'none', }; diff --git a/src/components/Nodes/DefaultNode.tsx b/src/components/Nodes/DefaultNode.tsx index 2ae1881e..1be3c7ba 100644 --- a/src/components/Nodes/DefaultNode.tsx +++ b/src/components/Nodes/DefaultNode.tsx @@ -1,19 +1,16 @@ -import React, { CSSProperties } from 'react'; +import React from 'react'; import Handle from '../../components/Handle'; import { NodeProps, Position } from '../../types'; -const nodeStyles: CSSProperties = { - background: '#ff6060', - padding: 10, - borderRadius: 5, - width: 150, -}; - -export default ({ data, targetPosition = Position.Top, sourcePosition = Position.Bottom, style }: NodeProps) => ( -
+const DefaultNode = ({ data, targetPosition = Position.Top, sourcePosition = Position.Bottom }: NodeProps) => ( + <> {data.label} -
+ ); + +DefaultNode.displayName = 'DefaultNode'; + +export default DefaultNode; diff --git a/src/components/Nodes/InputNode.tsx b/src/components/Nodes/InputNode.tsx index baf4bd15..8db56ef8 100644 --- a/src/components/Nodes/InputNode.tsx +++ b/src/components/Nodes/InputNode.tsx @@ -1,18 +1,15 @@ -import React, { CSSProperties } from 'react'; +import React from 'react'; import Handle from '../../components/Handle'; import { NodeProps, Position } from '../../types'; -const nodeStyles: CSSProperties = { - background: '#9999ff', - padding: 10, - borderRadius: 5, - width: 150, -}; - -export default ({ data, style, sourcePosition = Position.Bottom }: NodeProps) => ( -
+const InputNode = ({ data, sourcePosition = Position.Bottom }: NodeProps) => ( + <> {data.label} -
+ ); + +InputNode.displayName = 'InputNode'; + +export default InputNode; diff --git a/src/components/Nodes/OutputNode.tsx b/src/components/Nodes/OutputNode.tsx index 0188d8a3..3276e369 100644 --- a/src/components/Nodes/OutputNode.tsx +++ b/src/components/Nodes/OutputNode.tsx @@ -1,18 +1,15 @@ -import React, { CSSProperties } from 'react'; +import React from 'react'; import Handle from '../../components/Handle'; import { NodeProps, Position } from '../../types'; -const nodeStyles: CSSProperties = { - background: '#55dd99', - padding: 10, - borderRadius: 5, - width: 150, -}; - -export default ({ data, style, targetPosition = Position.Top }: NodeProps) => ( -
+const OutputNode = ({ data, targetPosition = Position.Top }: NodeProps) => ( + <> {data.label} -
+ ); + +OutputNode.displayName = 'OutputNode'; + +export default OutputNode; diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index a1fb111a..45faf948 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -158,23 +158,16 @@ export default (NodeComponent: ComponentType) => { zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)`, pointerEvents: isInteractive ? 'all' : 'none', - }; - - const updateNode = (): void => { - if (!nodeElement.current) { - return; - } - - store.dispatch.updateNodeDimensions({ id, nodeElement: nodeElement.current }); + ...style, }; useEffect(() => { if (nodeElement.current) { - updateNode(); + store.dispatch.updateNodeDimensions({ id, nodeElement: nodeElement.current }); const resizeObserver = new ResizeObserver((entries) => { for (let _ of entries) { - updateNode(); + store.dispatch.updateNodeDimensions({ id, nodeElement: nodeElement.current! }); } }); @@ -188,7 +181,7 @@ export default (NodeComponent: ComponentType) => { } return; - }, []); + }, [id]); return ( ) => { id={id} data={data} type={type} - style={style} selected={selected} sourcePosition={sourcePosition} targetPosition={targetPosition} diff --git a/src/style.css b/src/style.css index 5232f445..c23938f1 100644 --- a/src/style.css +++ b/src/style.css @@ -60,6 +60,12 @@ } } +@keyframes dashdraw { + from { + stroke-dashoffset: 10; + } +} + .react-flow__connection { pointer-events: none; } @@ -85,12 +91,6 @@ fill: white; } -@keyframes dashdraw { - from { - stroke-dashoffset: 10; - } -} - .react-flow__nodes { width: 100%; height: 100%; @@ -110,13 +110,17 @@ pointer-events: all; transform-origin: 0 0; cursor: grab; + padding: 10px; + border-radius: 5px; + width: 150px; + background: #eee; - &.selected > *, - &.selected:hover > * { + &.selected, + &.selected:hover { box-shadow: 0 0 0 2px #555; } - &:hover > * { + &:hover { box-shadow: 0 1px 5px 2px rgba(0, 0, 0, 0.08); } @@ -125,6 +129,18 @@ } } +.react-flow__node-default { + background: #ff6060; +} + +.react-flow__node-input { + background: #9999ff; +} + +.react-flow__node-output { + background: #55dd99; +} + .react-flow__handle { position: absolute; width: 10px; @@ -173,29 +189,4 @@ border: 1px dotted rgba(0, 89, 220, 0.8); pointer-events: all; } -} - -.react-flow__controls { - box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08); - - &-button { - background: #fefefe; - border-bottom: 1px solid #eee; - display: flex; - justify-content: center; - align-items: center; - width: 16px; - height: 16px; - cursor: pointer; - user-select: none; - padding: 5px; - - svg { - width: 100%; - } - - &:hover { - background: #f4f4f4; - } - } -} +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index b45da6e4..ce324c63 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -96,7 +96,6 @@ export interface NodeProps { selected: boolean; targetPosition?: Position; sourcePosition?: Position; - style?: CSSProperties; } export interface NodeComponentProps { From c620d0f40c17847643292f487c7ace7f8b9068af Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 29 May 2020 18:53:23 +0200 Subject: [PATCH 3/4] refactor(connectionLine): create connection line type enum --- src/components/ConnectionLine/index.tsx | 10 +++++----- src/container/EdgeRenderer/index.tsx | 4 ++-- src/container/GraphView/index.tsx | 15 ++++++++++++--- src/container/ReactFlow/index.tsx | 18 +++++++++++++----- src/types/index.ts | 5 +++++ 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 9eec2b54..b2d81d37 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useState, CSSProperties } from 'react'; import cx from 'classnames'; -import { ElementId, Node, Transform, HandleElement, Position } from '../../types'; +import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType } from '../../types'; interface ConnectionLineProps { connectionSourceId: ElementId; connectionPositionX: number; connectionPositionY: number; - connectionLineType?: string | null; + connectionLineType: ConnectionLineType; nodes: Node[]; transform: Transform; isInteractive: boolean; @@ -17,10 +17,10 @@ interface ConnectionLineProps { export default ({ connectionSourceId, - connectionLineStyle = {}, + connectionLineStyle, connectionPositionX, connectionPositionY, - connectionLineType, + connectionLineType = ConnectionLineType.Bezier, nodes = [], className, transform, @@ -60,7 +60,7 @@ export default ({ let dAttr: string = ''; - if (connectionLineType === 'bezier') { + if (connectionLineType === ConnectionLineType.Bezier) { if (sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right) { const xOffset = Math.abs(targetX - sourceX) / 2; const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index a25b0167..762e4175 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -3,14 +3,14 @@ import React, { memo, CSSProperties } from 'react'; import { useStoreState } from '../../store/hooks'; import ConnectionLine from '../../components/ConnectionLine/index'; import { isEdge } from '../../utils/graph'; -import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements } from '../../types'; +import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements, ConnectionLineType } from '../../types'; interface EdgeRendererProps { width: number; height: number; edgeTypes: any; + connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; - connectionLineType?: string; onElementClick?: (element: Node | Edge) => void; } diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index 54e8450b..1dca125c 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -12,7 +12,16 @@ import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; import useElementUpdater from '../../hooks/useElementUpdater'; import { getDimensions } from '../../utils'; import { fitView, zoomIn, zoomOut, project } from '../../utils/graph'; -import { Elements, NodeTypesType, EdgeTypesType, OnLoadFunc, Node, Edge, Connection } from '../../types'; +import { + Elements, + NodeTypesType, + EdgeTypesType, + OnLoadFunc, + Node, + Edge, + Connection, + ConnectionLineType, +} from '../../types'; export interface GraphViewProps { elements: Elements; @@ -26,8 +35,8 @@ export interface GraphViewProps { selectionKeyCode: number; nodeTypes: NodeTypesType; edgeTypes: EdgeTypesType; - connectionLineType: string; - connectionLineStyle: CSSProperties; + connectionLineType: ConnectionLineType; + connectionLineStyle?: CSSProperties; deleteKeyCode: number; snapToGrid: boolean; snapGrid: [number, number]; diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index c3c1d99a..1339529e 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -20,7 +20,16 @@ import StraightEdge from '../../components/Edges/StraightEdge'; import StepEdge from '../../components/Edges/StepEdge'; import { createEdgeTypes } from '../EdgeRenderer/utils'; import store from '../../store'; -import { Elements, NodeTypesType, EdgeTypesType, OnLoadFunc, Node, Edge, Connection } from '../../types'; +import { + Elements, + NodeTypesType, + EdgeTypesType, + OnLoadFunc, + Node, + Edge, + Connection, + ConnectionLineType, +} from '../../types'; import '../../style.css'; @@ -36,8 +45,8 @@ export interface ReactFlowProps extends Omit, 'on onSelectionChange?: (elements: Elements | null) => void; nodeTypes: NodeTypesType; edgeTypes: EdgeTypesType; - connectionLineType: string; - connectionLineStyle: CSSProperties; + connectionLineType: ConnectionLineType; + connectionLineStyle?: CSSProperties; deleteKeyCode: number; selectionKeyCode: number; snapToGrid: boolean; @@ -119,8 +128,7 @@ ReactFlow.defaultProps = { straight: StraightEdge, step: StepEdge, }, - connectionLineType: 'bezier', - connectionLineStyle: {}, + connectionLineType: ConnectionLineType.Bezier, deleteKeyCode: 8, selectionKeyCode: 16, snapToGrid: false, diff --git a/src/types/index.ts b/src/types/index.ts index ce324c63..8a29f12b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -153,6 +153,11 @@ export interface Connection { target: ElementId | null; } +export enum ConnectionLineType { + Bezier = 'bezier', + Straight = 'straight', +} + export type OnConnectFunc = (connection: Connection) => void; export interface HandleElement extends XYPosition, Dimensions { From 0558ff86c4eed543c8dabb820b0f9999315b7197 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 30 May 2020 20:26:55 +0200 Subject: [PATCH 4/4] fix(bezieredge): correctly connect diagonally nodes closes #256 --- example/src/Horizontal/index.js | 15 ++++++++++++--- src/components/Edges/BezierEdge.tsx | 5 ++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/example/src/Horizontal/index.js b/example/src/Horizontal/index.js index 47684f5d..d7fb9f68 100644 --- a/example/src/Horizontal/index.js +++ b/example/src/Horizontal/index.js @@ -9,11 +9,20 @@ const onLoad = (graph) => { const initialElements = [ { id: '1', sourcePosition: 'right', type: 'input', className: 'dark-node', data: { label: 'Input' }, position: { x: 0, y: 80 } }, { id: '2', sourcePosition: 'right', targetPosition: 'left', data: { label: 'A Node' }, position: { x: 250, y: 0 } }, - { id: '3', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Another node' }, position: { x: 250, y: 160 } }, - { id: '4', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 4' }, position: { x: 500, y: 80 } }, + { id: '3', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 3' }, position: { x: 250, y: 160 } }, + { id: '4', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 4' }, position: { x: 500, y: 0 } }, + { id: '5', sourcePosition: 'top', targetPosition: 'bottom', data: { label: 'Node 5' }, position: { x: 500, y: 100 } }, + { id: '6', sourcePosition: 'bottom', targetPosition: 'top', data: { label: 'Node 6' }, position: { x: 500, y: 230 } }, + { id: '7', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 7' }, position: { x: 750, y: 50 } }, + { id: '8', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 8' }, position: { x: 750, y: 300 } }, + { id: 'e1-2', source: '1', target: '2', animated: true, }, { id: 'e1-3', source: '1', target: '3', animated: true, }, - { id: 'e1-4', source: '2', target: '4', label: 'edge label' } + { id: 'e1-4', source: '2', target: '4', label: 'edge label' }, + { id: 'e3-5', source: '3', target: '5', animated: true }, + { id: 'e3-6', source: '3', target: '6', animated: true }, + { id: 'e5-7', source: '5', target: '7', animated: true }, + { id: 'e6-8', source: '6', target: '8', animated: true }, ]; const HorizontalFlow = () => { diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index b349c868..a6bdc8a5 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -26,10 +26,13 @@ export default memo( 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)) { dAttr = `M${sourceX},${sourceY} C${centerX},${sourceY} ${centerX},${targetY} ${targetX},${targetY}`; - } else if (leftAndRight.includes(sourcePosition) || leftAndRight.includes(targetPosition)) { + } else if (leftAndRight.includes(targetPosition)) { dAttr = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}`; + } else if (leftAndRight.includes(sourcePosition)) { + dAttr = `M${sourceX},${sourceY} C${targetX},${sourceY} ${targetX},${sourceY} ${targetX},${targetY}`; } const text = label ? (