Merge branch 'main' into v10
This commit is contained in:
@@ -35,6 +35,11 @@ const elements = [
|
|||||||
const BasicFlow = () => <ReactFlow elements={elements} />;
|
const BasicFlow = () => <ReactFlow elements={elements} />;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Community Packages
|
||||||
|
|
||||||
|
* [react-flow-smart-edge](https://github.com/tisoap/react-flow-smart-edge) - Custom edge that doesn't intersect with nodes
|
||||||
|
* [Feliz.ReactFlow](https://github.com/tforkmann/Feliz.ReactFlow) - Feliz React Bindings for React Flow
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Before you start you need to install the React Flow dependencies via `npm install` and the ones of the examples `cd example && npm install`.
|
Before you start you need to install the React Flow dependencies via `npm install` and the ones of the examples `cd example && npm install`.
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ const UpdateNodeInternalsFlow = () => {
|
|||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onPaneClick={onPaneClick}
|
onPaneClick={onPaneClick}
|
||||||
connectionLineType={ConnectionLineType.SmoothStep}
|
connectionLineType={ConnectionLineType.Bezier}
|
||||||
connectionMode={ConnectionMode.Loose}
|
connectionMode={ConnectionMode.Loose}
|
||||||
onEdgeUpdate={onEdgeUpdate}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const UseZoomPanHelperFlow = () => {
|
|||||||
const [elements, setElements] = useState<Elements>(initialElements);
|
const [elements, setElements] = useState<Elements>(initialElements);
|
||||||
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
|
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
|
||||||
const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els));
|
const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els));
|
||||||
const { project } = useZoomPanHelper();
|
const { project, setCenter, zoomIn, zoomOut } = useZoomPanHelper();
|
||||||
|
|
||||||
const onPaneClick = useCallback(
|
const onPaneClick = useCallback(
|
||||||
(evt) => {
|
(evt) => {
|
||||||
@@ -48,8 +48,26 @@ const UseZoomPanHelperFlow = () => {
|
|||||||
[project]
|
[project]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onElementClick = useCallback(
|
||||||
|
(_, element) => {
|
||||||
|
const { x, y } = element.position;
|
||||||
|
setCenter(x, y, 1);
|
||||||
|
},
|
||||||
|
[setCenter]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow elements={elements} onElementsRemove={onElementsRemove} onConnect={onConnect} onPaneClick={onPaneClick}>
|
<ReactFlow
|
||||||
|
elements={elements}
|
||||||
|
onElementClick={onElementClick}
|
||||||
|
onElementsRemove={onElementsRemove}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onPaneClick={onPaneClick}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'absolute', left: 0, top: 0, zIndex: 100 }}>
|
||||||
|
<button onClick={() => zoomIn(1200)}>zoomIn</button>
|
||||||
|
<button onClick={() => zoomOut(0)}>zoomOut</button>
|
||||||
|
</div>
|
||||||
<Background />
|
<Background />
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|||||||
Generated
+2079
-2713
File diff suppressed because it is too large
Load Diff
+22
-25
@@ -31,54 +31,51 @@
|
|||||||
"release:next": "release-it --preRelease=next --no-git"
|
"release:next": "release-it --preRelease=next --no-git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.15.4",
|
"@babel/runtime": "^7.16.3",
|
||||||
"@types/d3": "^7.0.0",
|
"@types/d3": "^7.1.0",
|
||||||
"@types/react-redux": "^7.1.19",
|
"@types/react-redux": "^7.1.20",
|
||||||
"classcat": "^5.0.3",
|
"classcat": "^5.0.3",
|
||||||
"d3-selection": "^3.0.0",
|
"d3-selection": "^3.0.0",
|
||||||
"d3-zoom": "^3.0.0",
|
"d3-zoom": "^3.0.0",
|
||||||
"react-draggable": "^4.4.4",
|
"react-draggable": "^4.4.4",
|
||||||
"react-redux": "^7.2.5",
|
"zustand": "^3.6.6"
|
||||||
"redux": "^4.1.1",
|
|
||||||
"redux-thunk": "^2.3.0",
|
|
||||||
"zustand": "^3.5.13"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.8",
|
"@babel/core": "^7.16.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.15.8",
|
"@babel/plugin-transform-runtime": "^7.16.4",
|
||||||
"@babel/preset-env": "^7.15.8",
|
"@babel/preset-env": "^7.16.4",
|
||||||
"@babel/preset-react": "^7.14.5",
|
"@babel/preset-react": "^7.16.0",
|
||||||
"@rollup/plugin-babel": "^5.3.0",
|
"@rollup/plugin-babel": "^5.3.0",
|
||||||
"@rollup/plugin-commonjs": "^21.0.0",
|
"@rollup/plugin-commonjs": "^21.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^13.0.5",
|
"@rollup/plugin-node-resolve": "^13.0.6",
|
||||||
"@rollup/plugin-replace": "^3.0.0",
|
"@rollup/plugin-replace": "^3.0.0",
|
||||||
"@svgr/rollup": "^5.5.0",
|
"@svgr/rollup": "^6.1.0",
|
||||||
"@types/redux": "^3.6.31",
|
"@types/redux": "^3.6.31",
|
||||||
"@types/resize-observer-browser": "^0.1.6",
|
"@types/resize-observer-browser": "^0.1.6",
|
||||||
"@welldone-software/why-did-you-render": "^6.2.1",
|
"@welldone-software/why-did-you-render": "^6.2.1",
|
||||||
"autoprefixer": "^10.3.6",
|
"autoprefixer": "^10.3.6",
|
||||||
"babel-preset-react-app": "^10.0.0",
|
"babel-preset-react-app": "^10.0.0",
|
||||||
"cypress": "^8.5.0",
|
"cypress": "^9.1.0",
|
||||||
"postcss": "^8.3.9",
|
"postcss": "^8.4.4",
|
||||||
"postcss-cli": "^9.0.1",
|
"postcss-cli": "^9.0.2",
|
||||||
"postcss-nested": "^5.0.6",
|
"postcss-nested": "^5.0.6",
|
||||||
"prettier": "2.4.1",
|
"prettier": "2.5.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"release-it": "^14.11.6",
|
"release-it": "^14.11.8",
|
||||||
"rollup": "^2.58.0",
|
"rollup": "^2.60.1",
|
||||||
"rollup-plugin-bundle-size": "^1.0.3",
|
"rollup-plugin-bundle-size": "^1.0.3",
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
"rollup-plugin-postcss": "^4.0.1",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-serve": "^1.1.0",
|
"rollup-plugin-serve": "^1.1.0",
|
||||||
"rollup-plugin-typescript2": "^0.30.0",
|
"rollup-plugin-typescript2": "^0.31.1",
|
||||||
"start-server-and-test": "^1.14.0",
|
"start-server-and-test": "^1.14.0",
|
||||||
"typescript": "^4.4.3"
|
"typescript": "^4.5.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "16 || 17",
|
"react": "16 || 17",
|
||||||
"react-dom": "16 || 17"
|
"react-dom": "16 || 17"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
|
|||||||
export interface ControlButtonProps extends HTMLAttributes<HTMLButtonElement> {}
|
export interface ControlButtonProps extends HTMLAttributes<HTMLButtonElement> {}
|
||||||
|
|
||||||
export const ControlButton: FC<ControlButtonProps> = ({ children, className, ...rest }) => (
|
export const ControlButton: FC<ControlButtonProps> = ({ children, className, ...rest }) => (
|
||||||
<button className={cc(['react-flow__controls-button', className])} {...rest}>
|
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ interface ConnectionLineProps {
|
|||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform });
|
const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform });
|
||||||
|
|
||||||
|
const getSourceHandle = (handleId: string | null, sourceNode: NodeInternalsItem, connectionHandleType: HandleType) => {
|
||||||
|
const handleTypeInverted = connectionHandleType === 'source' ? 'target' : 'source';
|
||||||
|
const handleBound =
|
||||||
|
sourceNode.handleBounds?.[connectionHandleType] || sourceNode.handleBounds?.[handleTypeInverted];
|
||||||
|
|
||||||
|
return handleId ? handleBound?.find((d: HandleElement) => d.id === handleId) : handleBound?.[0];
|
||||||
|
};
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
connectionNodeId,
|
connectionNodeId,
|
||||||
connectionHandleId,
|
connectionHandleId,
|
||||||
@@ -44,25 +52,22 @@ export default ({
|
|||||||
const handleId = connectionHandleId;
|
const handleId = connectionHandleId;
|
||||||
|
|
||||||
const { nodeInternals, transform } = useStore(selector, shallow);
|
const { nodeInternals, transform } = useStore(selector, shallow);
|
||||||
const sourceNodeInternals = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId));
|
const sourceNode = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId));
|
||||||
const sourceNode = useRef<Node | undefined>(nodeInternals.get(nodeId));
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!sourceNode.current ||
|
!sourceNode.current ||
|
||||||
!sourceNodeInternals.current ||
|
!sourceNode.current ||
|
||||||
!isConnectable ||
|
!isConnectable ||
|
||||||
!sourceNodeInternals.current.handleBounds?.[connectionHandleType]
|
!sourceNode.current.handleBounds?.[connectionHandleType]
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceHandle = handleId
|
const sourceHandle = getSourceHandle(handleId, sourceNode.current, connectionHandleType);
|
||||||
? sourceNodeInternals.current.handleBounds[connectionHandleType]!.find((d: HandleElement) => d.id === handleId)
|
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (sourceNode.current?.width ?? 0) / 2;
|
||||||
: sourceNodeInternals.current.handleBounds[connectionHandleType]![0];
|
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.current?.height ?? 0;
|
||||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNodeInternals.current.width! / 2;
|
const sourceX = sourceNode.current.positionAbsolute!.x + sourceHandleX;
|
||||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNodeInternals.current.height!;
|
const sourceY = sourceNode.current.positionAbsolute!.y + sourceHandleY;
|
||||||
const sourceX = sourceNodeInternals.current.positionAbsolute!.x + sourceHandleX;
|
|
||||||
const sourceY = sourceNodeInternals.current.positionAbsolute!.y + sourceHandleY;
|
|
||||||
|
|
||||||
const targetX = (connectionPositionX - transform[0]) / transform[2];
|
const targetX = (connectionPositionX - transform[0]) / transform[2];
|
||||||
const targetY = (connectionPositionY - transform[1]) / transform[2];
|
const targetY = (connectionPositionY - transform[1]) / transform[2];
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ export function getSmoothStepPath({
|
|||||||
sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize);
|
sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize);
|
||||||
secondCornerPath =
|
secondCornerPath =
|
||||||
sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize);
|
sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize);
|
||||||
} else if (sourcePosition === Position.Right && targetPosition === Position.Left) {
|
} else if (
|
||||||
|
(sourcePosition === Position.Right && targetPosition === Position.Left) ||
|
||||||
|
(sourcePosition === Position.Left && targetPosition === Position.Right) ||
|
||||||
|
(sourcePosition === Position.Left && targetPosition === Position.Left)
|
||||||
|
) {
|
||||||
// and sourceX > targetX
|
// and sourceX > targetX
|
||||||
firstCornerPath =
|
firstCornerPath =
|
||||||
sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize);
|
sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { zoomIdentity } from 'd3-zoom';
|
import { zoomIdentity } from 'd3-zoom';
|
||||||
import shallow from 'zustand/shallow';
|
import shallow from 'zustand/shallow';
|
||||||
|
import { Selection as D3Selection } from 'd3';
|
||||||
|
|
||||||
import { useStoreApi, useStore } from '../store';
|
import { useStoreApi, useStore } from '../store';
|
||||||
import { getRectOfNodeInternals, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
|
import { getRectOfNodeInternals, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
|
||||||
@@ -25,6 +26,10 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
d3Selection: s.d3Selection,
|
d3Selection: s.d3Selection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getTransition = (selection: D3Selection<Element, unknown, null, undefined>, duration: number = 0) => {
|
||||||
|
return selection.transition().duration(duration);
|
||||||
|
};
|
||||||
|
|
||||||
const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { d3Zoom, d3Selection } = useStore(selector, shallow);
|
const { d3Zoom, d3Selection } = useStore(selector, shallow);
|
||||||
@@ -32,53 +37,54 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
|||||||
const zoomPanHelperFunctions = useMemo<ZoomPanHelperFunctions>(() => {
|
const zoomPanHelperFunctions = useMemo<ZoomPanHelperFunctions>(() => {
|
||||||
if (d3Selection && d3Zoom) {
|
if (d3Selection && d3Zoom) {
|
||||||
return {
|
return {
|
||||||
zoomIn: () => d3Zoom.scaleBy(d3Selection, 1.2),
|
zoomIn: (options) => d3Zoom.scaleBy(getTransition(d3Selection, options?.duration), 1.2),
|
||||||
zoomOut: () => d3Zoom.scaleBy(d3Selection, 1 / 1.2),
|
zoomOut: (options) => d3Zoom.scaleBy(getTransition(d3Selection, options?.duration), 1 / 1.2),
|
||||||
zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel),
|
zoomTo: (zoomLevel, options) =>
|
||||||
|
d3Zoom.scaleTo(getTransition(d3Selection, options?.duration), zoomLevel),
|
||||||
transform: (transform: FlowTransform) => {
|
transform: (transform: FlowTransform) => {
|
||||||
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
|
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
|
||||||
|
|
||||||
d3Zoom.transform(d3Selection, nextTransform);
|
d3Zoom.transform(getTransition(d3Selection), nextTransform);
|
||||||
},
|
},
|
||||||
fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {
|
fitView: (options) => {
|
||||||
const { nodeInternals, width, height, minZoom, maxZoom } = store.getState();
|
const { nodeInternals, width, height, minZoom, maxZoom } = store.getState();
|
||||||
// @TODO: work with nodeInternals instead of converting it to an array
|
// @TODO: work with nodeInternals instead of converting it to an array
|
||||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||||
|
|
||||||
if (!nodes.length) {
|
if (!nodes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = getRectOfNodeInternals(
|
const bounds = getRectOfNodeInternals(
|
||||||
options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
|
options?.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
|
||||||
);
|
);
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
const [x, y, zoom] = getTransformForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
options.minZoom ?? minZoom,
|
options?.minZoom ?? minZoom,
|
||||||
options.maxZoom ?? maxZoom,
|
options?.maxZoom ?? maxZoom,
|
||||||
options.padding ?? DEFAULT_PADDING
|
options?.padding ?? DEFAULT_PADDING
|
||||||
);
|
);
|
||||||
const transform = zoomIdentity.translate(x, y).scale(zoom);
|
const transform = zoomIdentity.translate(x, y).scale(zoom);
|
||||||
|
|
||||||
d3Zoom.transform(d3Selection, transform);
|
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform);
|
||||||
},
|
},
|
||||||
setCenter: (x: number, y: number, zoom?: number) => {
|
setCenter: (x, y, options) => {
|
||||||
const { width, height, maxZoom } = store.getState();
|
const { width, height, maxZoom } = store.getState();
|
||||||
|
|
||||||
const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom;
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
||||||
const centerX = width / 2 - x * nextZoom;
|
const centerX = width / 2 - x * nextZoom;
|
||||||
const centerY = height / 2 - y * nextZoom;
|
const centerY = height / 2 - y * nextZoom;
|
||||||
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
|
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
|
||||||
|
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform);
|
||||||
d3Zoom.transform(d3Selection, transform);
|
|
||||||
},
|
},
|
||||||
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
|
fitBounds: (bounds, options) => {
|
||||||
const { width, height, minZoom, maxZoom } = store.getState();
|
const { width, height, minZoom, maxZoom } = store.getState();
|
||||||
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
|
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? DEFAULT_PADDING);
|
||||||
const transform = zoomIdentity.translate(x, y).scale(zoom);
|
const transform = zoomIdentity.translate(x, y).scale(zoom);
|
||||||
|
|
||||||
d3Zoom.transform(d3Selection, transform);
|
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform);
|
||||||
},
|
},
|
||||||
project: (position: XYPosition) => {
|
project: (position: XYPosition) => {
|
||||||
const { transform, snapToGrid, snapGrid } = store.getState();
|
const { transform, snapToGrid, snapGrid } = store.getState();
|
||||||
|
|||||||
+14
-3
@@ -3,7 +3,10 @@
|
|||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"lib": ["dom", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
@@ -21,6 +24,14 @@
|
|||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": [
|
||||||
"exclude": ["node_modules", "build", "dist", "example", "rollup.config.js"]
|
"src"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"example",
|
||||||
|
"rollup.config.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user