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 {