Merge pull request #2738 from wbkd/fix/fitview-subflow
Fix/fitview subflow
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@reactflow/core': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: fitView for subflows, context manu on right mouse pan
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 3000 --open",
|
"dev": "vite --port 3000 --open",
|
||||||
"serve": "vite serve --port 3000",
|
"serve": "vite serve --port 3000",
|
||||||
|
"build": "vite build",
|
||||||
"test:dev": "cypress open",
|
"test:dev": "cypress open",
|
||||||
"test": "pnpm test-component && pnpm test-e2e",
|
"test": "pnpm test-component && pnpm test-e2e",
|
||||||
"test-component": "cypress run --component",
|
"test-component": "cypress run --component",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
|||||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||||
|
|
||||||
const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
|
const defaultViewport = { x: 0, y: 0, zoom: 1.5 };
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
const initialNodes: Node[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ const ZoomPane = ({
|
|||||||
const prevTransform = useRef<Viewport>({ x: 0, y: 0, zoom: 0 });
|
const prevTransform = useRef<Viewport>({ x: 0, y: 0, zoom: 0 });
|
||||||
const { d3Zoom, d3Selection, d3ZoomHandler, userSelectionActive } = useStore(selector, shallow);
|
const { d3Zoom, d3Selection, d3ZoomHandler, userSelectionActive } = useStore(selector, shallow);
|
||||||
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
|
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
|
||||||
|
const mouseButton = useRef<number>(0);
|
||||||
|
|
||||||
useResizeHandler(zoomPane);
|
useResizeHandler(zoomPane);
|
||||||
|
|
||||||
@@ -164,30 +165,6 @@ const ZoomPane = ({
|
|||||||
noWheelClassName,
|
noWheelClassName,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (d3Zoom) {
|
|
||||||
if (userSelectionActive && !isZoomingOrPanning.current) {
|
|
||||||
d3Zoom.on('zoom', null);
|
|
||||||
} else if (!userSelectionActive) {
|
|
||||||
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
|
||||||
const { onViewportChange } = store.getState();
|
|
||||||
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
|
||||||
|
|
||||||
zoomedWithRightMouseButton.current = !!(
|
|
||||||
onPaneContextMenu && isRightClickPan(panOnDrag, event.sourceEvent?.button)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (onMove || onViewportChange) {
|
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
|
||||||
|
|
||||||
onViewportChange?.(flowTransform);
|
|
||||||
onMove?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [userSelectionActive, d3Zoom, onMove, panOnDrag, onPaneContextMenu]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (d3Zoom) {
|
if (d3Zoom) {
|
||||||
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
@@ -195,6 +172,9 @@ const ZoomPane = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need to remember it here, because it's always 0 in the "zoom" event
|
||||||
|
mouseButton.current = event.sourceEvent.button;
|
||||||
|
|
||||||
const { onViewportChangeStart } = store.getState();
|
const { onViewportChangeStart } = store.getState();
|
||||||
isZoomingOrPanning.current = true;
|
isZoomingOrPanning.current = true;
|
||||||
|
|
||||||
@@ -213,6 +193,30 @@ const ZoomPane = ({
|
|||||||
}
|
}
|
||||||
}, [d3Zoom, onMoveStart]);
|
}, [d3Zoom, onMoveStart]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (d3Zoom) {
|
||||||
|
if (userSelectionActive && !isZoomingOrPanning.current) {
|
||||||
|
d3Zoom.on('zoom', null);
|
||||||
|
} else if (!userSelectionActive) {
|
||||||
|
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
|
const { onViewportChange } = store.getState();
|
||||||
|
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
||||||
|
|
||||||
|
zoomedWithRightMouseButton.current = !!(
|
||||||
|
onPaneContextMenu && isRightClickPan(panOnDrag, mouseButton.current ?? 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (onMove || onViewportChange) {
|
||||||
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
|
|
||||||
|
onViewportChange?.(flowTransform);
|
||||||
|
onMove?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userSelectionActive, d3Zoom, onMove, panOnDrag, onPaneContextMenu]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (d3Zoom) {
|
if (d3Zoom) {
|
||||||
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
@@ -226,7 +230,7 @@ const ZoomPane = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
onPaneContextMenu &&
|
onPaneContextMenu &&
|
||||||
isRightClickPan(panOnDrag, event.sourceEvent?.button) &&
|
isRightClickPan(panOnDrag, mouseButton.current ?? 0) &&
|
||||||
!zoomedWithRightMouseButton.current
|
!zoomedWithRightMouseButton.current
|
||||||
) {
|
) {
|
||||||
onPaneContextMenu(event.sourceEvent);
|
onPaneContextMenu(event.sourceEvent);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createStore } from 'zustand';
|
|||||||
import { clampPosition, getDimensions, internalsSymbol } from '../utils';
|
import { clampPosition, getDimensions, internalsSymbol } from '../utils';
|
||||||
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
import { createNodeInternals, fitView, updateNodesAndEdgesSelections } from './utils';
|
import { createNodeInternals, fitView, updateAbsoluteNodePositions, updateNodesAndEdgesSelections } from './utils';
|
||||||
import initialState from './initialState';
|
import initialState from './initialState';
|
||||||
import type {
|
import type {
|
||||||
ReactFlowState,
|
ReactFlowState,
|
||||||
@@ -99,6 +99,8 @@ const createRFStore = () =>
|
|||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
updateAbsoluteNodePositions(nodeInternals, nodeOrigin);
|
||||||
|
|
||||||
const nextFitViewOnInitDone =
|
const nextFitViewOnInitDone =
|
||||||
fitViewOnInitDone ||
|
fitViewOnInitDone ||
|
||||||
(fitViewOnInit && !fitViewOnInitDone && fitView(get, { initial: true, ...fitViewOnInitOptions }));
|
(fitViewOnInit && !fitViewOnInitDone && fitView(get, { initial: true, ...fitViewOnInitOptions }));
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ type ParentNodes = Record<string, boolean>;
|
|||||||
function calculateXYZPosition(
|
function calculateXYZPosition(
|
||||||
node: Node,
|
node: Node,
|
||||||
nodeInternals: NodeInternals,
|
nodeInternals: NodeInternals,
|
||||||
parentNodes: ParentNodes,
|
|
||||||
result: XYZPosition,
|
result: XYZPosition,
|
||||||
nodeOrigin: NodeOrigin
|
nodeOrigin: NodeOrigin
|
||||||
): XYZPosition {
|
): XYZPosition {
|
||||||
@@ -33,7 +32,6 @@ function calculateXYZPosition(
|
|||||||
return calculateXYZPosition(
|
return calculateXYZPosition(
|
||||||
parentNode,
|
parentNode,
|
||||||
nodeInternals,
|
nodeInternals,
|
||||||
parentNodes,
|
|
||||||
{
|
{
|
||||||
x: (result.x ?? 0) + parentNodePosition.x,
|
x: (result.x ?? 0) + parentNodePosition.x,
|
||||||
y: (result.y ?? 0) + parentNodePosition.y,
|
y: (result.y ?? 0) + parentNodePosition.y,
|
||||||
@@ -43,6 +41,41 @@ function calculateXYZPosition(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateAbsoluteNodePositions(
|
||||||
|
nodeInternals: NodeInternals,
|
||||||
|
nodeOrigin: NodeOrigin,
|
||||||
|
parentNodes?: ParentNodes
|
||||||
|
) {
|
||||||
|
nodeInternals.forEach((node) => {
|
||||||
|
if (node.parentNode && !nodeInternals.has(node.parentNode)) {
|
||||||
|
throw new Error(`Parent node ${node.parentNode} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.parentNode || parentNodes?.[node.id]) {
|
||||||
|
const { x, y, z } = calculateXYZPosition(
|
||||||
|
node,
|
||||||
|
nodeInternals,
|
||||||
|
{
|
||||||
|
...node.position,
|
||||||
|
z: node[internalsSymbol]?.z ?? 0,
|
||||||
|
},
|
||||||
|
nodeOrigin
|
||||||
|
);
|
||||||
|
|
||||||
|
node.positionAbsolute = {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
};
|
||||||
|
|
||||||
|
node[internalsSymbol]!.z = z;
|
||||||
|
|
||||||
|
if (parentNodes?.[node.id]) {
|
||||||
|
node[internalsSymbol]!.isParent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createNodeInternals(
|
export function createNodeInternals(
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
nodeInternals: NodeInternals,
|
nodeInternals: NodeInternals,
|
||||||
@@ -83,35 +116,7 @@ export function createNodeInternals(
|
|||||||
nextNodeInternals.set(node.id, internals);
|
nextNodeInternals.set(node.id, internals);
|
||||||
});
|
});
|
||||||
|
|
||||||
nextNodeInternals.forEach((node) => {
|
updateAbsoluteNodePositions(nextNodeInternals, nodeOrigin, parentNodes);
|
||||||
if (node.parentNode && !nextNodeInternals.has(node.parentNode)) {
|
|
||||||
throw new Error(`Parent node ${node.parentNode} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.parentNode || parentNodes[node.id]) {
|
|
||||||
const { x, y, z } = calculateXYZPosition(
|
|
||||||
node,
|
|
||||||
nextNodeInternals,
|
|
||||||
parentNodes,
|
|
||||||
{
|
|
||||||
...node.position,
|
|
||||||
z: node[internalsSymbol]?.z ?? 0,
|
|
||||||
},
|
|
||||||
nodeOrigin
|
|
||||||
);
|
|
||||||
|
|
||||||
node.positionAbsolute = {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
};
|
|
||||||
|
|
||||||
node[internalsSymbol]!.z = z;
|
|
||||||
|
|
||||||
if (parentNodes[node.id]) {
|
|
||||||
node[internalsSymbol]!.isParent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return nextNodeInternals;
|
return nextNodeInternals;
|
||||||
}
|
}
|
||||||
@@ -142,6 +147,7 @@ export function fitView(get: StoreApi<ReactFlowState>['getState'], options: Inte
|
|||||||
|
|
||||||
if (nodes.length > 0 && nodesInitialized) {
|
if (nodes.length > 0 && nodesInitialized) {
|
||||||
const bounds = getRectOfNodes(nodes, nodeOrigin);
|
const bounds = getRectOfNodes(nodes, nodeOrigin);
|
||||||
|
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
const [x, y, zoom] = getTransformForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
width,
|
width,
|
||||||
|
|||||||
Reference in New Issue
Block a user