diff --git a/README.md b/README.md index 9fee03ef..ed6f8d43 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ React Flow is a library for building node-based graphs. You can easily implement - [ReactFlowProvider](#reactflowprovider) - [Styling](#styling) - [Helper Functions](#helper-functions) -- [Access Internal State](#access-internal-state) +- [Access Internal State and Actions](#access-internal-state-and-actions) - [Examples](#examples) - [Development](#development) - [Testing](#testing) @@ -156,6 +156,12 @@ Fits view port so that all nodes are visible `getElements = (): Elements` +### setTransform + +Sets position and zoom of the pane + +`setTransform = (transform: FlowTransform): void` + # Nodes There are three different [node types](#node-types--custom-nodes) (`default`, `input`, `output`) you can use. The node types differ in the number and types of handles. An input node has only a source handle, a default node has a source and a target and an output node has only a target handle. You create nodes by adding them to the `elements` array of the `ReactFlow` component. @@ -507,7 +513,7 @@ Returns all direct child nodes of the passed node You can use these function as seen in [this example](/example/src/Overview/index.js#L40-L41) or use your own ones. -# Access Internal State +# Access Internal State and Actions Under the hood React Flow uses [Easy Peasy](https://easy-peasy.now.sh/) for state handling. If you need to access the internal state you can use the `useStoreState` hook inside a child component of the `ReactFlow` component: @@ -530,6 +536,21 @@ const Flow = () => ( ); ``` +You will not need this in most cases but you can also use the internal actions that are defined in the [store](/src/store/index.ts): + +```javascript +import React, { useEffect } from 'react'; +import { useStoreActions } from 'react-flow-renderer' + +const TransformUpdater = ({ x, y, zoom }) => { + const setTransform = useStoreActions(a => a.setInitTransform); + + useEffect(() => { + setTransform({ x, y, k: zoom }) + }, [x, y, zoom]) +}); +``` + If you need more control you can wrap the `ReactFlow` component with the `ReactFlowProvider` component in order to be able to call `useStoreState` outside of the `ReactFlow` component. # Examples diff --git a/cypress/integration/flow/basic.spec.js b/cypress/integration/flow/basic.spec.js index 121896d3..39209e42 100644 --- a/cypress/integration/flow/basic.spec.js +++ b/cypress/integration/flow/basic.spec.js @@ -13,9 +13,6 @@ describe('Basic Flow Rendering', () => { it('renders a grid', () => { cy.get('.react-flow__background'); - - const gridStroke = Cypress.$('.react-flow__background path').attr('stroke'); - expect(gridStroke).to.equal('#eee'); }); it('selects a node by click', () => { diff --git a/example/src/Overview/index.js b/example/src/Overview/index.js index d2247c38..c7e3c64f 100644 --- a/example/src/Overview/index.js +++ b/example/src/Overview/index.js @@ -136,7 +136,7 @@ const OverviewFlow = () => { }} /> - + ); }; diff --git a/src/additional-components/Background/index.tsx b/src/additional-components/Background/index.tsx index 86a24a09..c655b544 100644 --- a/src/additional-components/Background/index.tsx +++ b/src/additional-components/Background/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, HTMLAttributes } from 'react'; +import React, { memo, useMemo, HTMLAttributes } from 'react'; import cc from 'classcat'; import { useStoreState } from '../../store/hooks'; @@ -21,26 +21,32 @@ const defaultColors = { const Background = memo( ({ 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); const bgClasses = cc(['react-flow__background', className]); - const bgColor = color ? color : defaultColors[variant]; const scaledGap = gap * scale; const xOffset = x % scaledGap; const yOffset = y % scaledGap; - const isLines = variant === BackgroundVariant.Lines; - const path = isLines - ? createGridLinesPath(width, height, xOffset, yOffset, scaledGap) - : createGridDotsPath(width, height, xOffset, yOffset, scaledGap, size); - const fill = isLines ? 'none' : bgColor; - const stroke = isLines ? bgColor : 'none'; + + const bgSvgTile = useMemo(() => { + const isLines = variant === BackgroundVariant.Lines; + const bgColor = color ? color : defaultColors[variant]; + const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor); + + return encodeURIComponent( + `${path}` + ); + }, [variant, scaledGap, size, color]); return ( - - - +
); } ); diff --git a/src/additional-components/Background/style.css b/src/additional-components/Background/style.css index b96234b9..9343180d 100644 --- a/src/additional-components/Background/style.css +++ b/src/additional-components/Background/style.css @@ -2,4 +2,6 @@ position: absolute; top: 0; left: 0; + width: 100%; + height: 100%; } \ No newline at end of file diff --git a/src/additional-components/Background/utils.ts b/src/additional-components/Background/utils.ts index cf938870..c0b6d315 100644 --- a/src/additional-components/Background/utils.ts +++ b/src/additional-components/Background/utils.ts @@ -1,37 +1,7 @@ -export const createGridLinesPath = ( - width: number, - height: number, - xOffset: number, - yOffset: number, - gap: number -): string => { - const lineCountX = Math.ceil(width / gap) + 1; - const lineCountY = Math.ceil(height / gap) + 1; - - const xValues = Array.from({ length: lineCountX }, (_, i) => `M${i * gap + xOffset} 0 V${height}`); - const yValues = Array.from({ length: lineCountY }, (_, i) => `M0 ${i * gap + yOffset} H${width}`); - - return [...xValues, ...yValues].join(' '); +export const createGridLinesPath = (scaledGap: number, strokeWidth: number, stroke: string): string => { + return ``; }; -export const createGridDotsPath = ( - width: number, - height: number, - xOffset: number, - yOffset: number, - gap: number, - size: number -): string => { - const lineCountX = Math.ceil(width / gap) + 1; - const lineCountY = Math.ceil(height / gap) + 1; - - const values = Array.from({ length: lineCountX }, (_, col) => { - const x = col * gap + xOffset; - return Array.from({ length: lineCountY }, (_, row) => { - const y = row * gap + yOffset; - return `M${x} ${y - size} l${size} ${size} l${-size} ${size} l${-size} ${-size}z`; - }).join(' '); - }); - - return values.join(' '); +export const createGridDotsPath = (size: number, fill: string): string => { + return ``; }; diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index 080d3921..b8503fc7 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -183,6 +183,8 @@ const GraphView = memo( zoomOut: () => zoom(-0.2), project, getElements, + setTransform: (transform: FlowTransform) => + setInitTransform({ x: transform.x, y: transform.y, k: transform.zoom }), }); } diff --git a/src/hooks/useElementUpdater.ts b/src/hooks/useElementUpdater.ts index 21c06677..a2a73b65 100644 --- a/src/hooks/useElementUpdater.ts +++ b/src/hooks/useElementUpdater.ts @@ -13,10 +13,8 @@ const useElementUpdater = (elements: Elements): void => { const setEdges = useStoreActions((a) => a.setEdges); useEffect(() => { - const nodes: Node[] = elements.filter(isNode); - const edges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge); - - const nextNodes: Node[] = nodes.map((propNode) => { + const nextEdges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge); + const nextNodes: Node[] = elements.filter(isNode).map((propNode) => { const existingNode = stateNodes.find((n) => n.id === propNode.id); if (existingNode) { @@ -83,14 +81,14 @@ const useElementUpdater = (elements: Elements): void => { }); const nodesChanged: boolean = !isEqual(stateNodes, nextNodes); - const edgesChanged: boolean = !isEqual(stateEdges, edges); + const edgesChanged: boolean = !isEqual(stateEdges, nextEdges); if (nodesChanged) { setNodes(nextNodes); } if (edgesChanged) { - setEdges(edges); + setEdges(nextEdges); } }, [elements, stateNodes, stateEdges]); }; diff --git a/src/types/index.ts b/src/types/index.ts index a362abaf..e4ce87ab 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -175,6 +175,7 @@ type OnLoadParams = { fitView: FitViewFunc; project: ProjectFunc; getElements: () => Elements; + setTransform: (transform: FlowTransform) => void; }; export type OnLoadFunc = (params: OnLoadParams) => void;