Merge branch 'main' into v10
This commit is contained in:
@@ -26,7 +26,7 @@ export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
|
||||
export interface ControlButtonProps extends HTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
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}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -29,6 +29,14 @@ interface ConnectionLineProps {
|
||||
|
||||
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 ({
|
||||
connectionNodeId,
|
||||
connectionHandleId,
|
||||
@@ -44,25 +52,22 @@ export default ({
|
||||
const handleId = connectionHandleId;
|
||||
|
||||
const { nodeInternals, transform } = useStore(selector, shallow);
|
||||
const sourceNodeInternals = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId));
|
||||
const sourceNode = useRef<Node | undefined>(nodeInternals.get(nodeId));
|
||||
const sourceNode = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId));
|
||||
|
||||
if (
|
||||
!sourceNode.current ||
|
||||
!sourceNodeInternals.current ||
|
||||
!sourceNode.current ||
|
||||
!isConnectable ||
|
||||
!sourceNodeInternals.current.handleBounds?.[connectionHandleType]
|
||||
!sourceNode.current.handleBounds?.[connectionHandleType]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sourceHandle = handleId
|
||||
? sourceNodeInternals.current.handleBounds[connectionHandleType]!.find((d: HandleElement) => d.id === handleId)
|
||||
: sourceNodeInternals.current.handleBounds[connectionHandleType]![0];
|
||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNodeInternals.current.width! / 2;
|
||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNodeInternals.current.height!;
|
||||
const sourceX = sourceNodeInternals.current.positionAbsolute!.x + sourceHandleX;
|
||||
const sourceY = sourceNodeInternals.current.positionAbsolute!.y + sourceHandleY;
|
||||
const sourceHandle = getSourceHandle(handleId, sourceNode.current, connectionHandleType);
|
||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (sourceNode.current?.width ?? 0) / 2;
|
||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.current?.height ?? 0;
|
||||
const sourceX = sourceNode.current.positionAbsolute!.x + sourceHandleX;
|
||||
const sourceY = sourceNode.current.positionAbsolute!.y + sourceHandleY;
|
||||
|
||||
const targetX = (connectionPositionX - transform[0]) / 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);
|
||||
secondCornerPath =
|
||||
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
|
||||
firstCornerPath =
|
||||
sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { zoomIdentity } from 'd3-zoom';
|
||||
import shallow from 'zustand/shallow';
|
||||
import { Selection as D3Selection } from 'd3';
|
||||
|
||||
import { useStoreApi, useStore } from '../store';
|
||||
import { getRectOfNodeInternals, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
|
||||
@@ -25,6 +26,10 @@ const selector = (s: ReactFlowState) => ({
|
||||
d3Selection: s.d3Selection,
|
||||
});
|
||||
|
||||
const getTransition = (selection: D3Selection<Element, unknown, null, undefined>, duration: number = 0) => {
|
||||
return selection.transition().duration(duration);
|
||||
};
|
||||
|
||||
const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
||||
const store = useStoreApi();
|
||||
const { d3Zoom, d3Selection } = useStore(selector, shallow);
|
||||
@@ -32,53 +37,54 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
|
||||
const zoomPanHelperFunctions = useMemo<ZoomPanHelperFunctions>(() => {
|
||||
if (d3Selection && d3Zoom) {
|
||||
return {
|
||||
zoomIn: () => d3Zoom.scaleBy(d3Selection, 1.2),
|
||||
zoomOut: () => d3Zoom.scaleBy(d3Selection, 1 / 1.2),
|
||||
zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel),
|
||||
zoomIn: (options) => d3Zoom.scaleBy(getTransition(d3Selection, options?.duration), 1.2),
|
||||
zoomOut: (options) => d3Zoom.scaleBy(getTransition(d3Selection, options?.duration), 1 / 1.2),
|
||||
zoomTo: (zoomLevel, options) =>
|
||||
d3Zoom.scaleTo(getTransition(d3Selection, options?.duration), zoomLevel),
|
||||
transform: (transform: FlowTransform) => {
|
||||
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();
|
||||
// @TODO: work with nodeInternals instead of converting it to an array
|
||||
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
|
||||
|
||||
if (!nodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = getRectOfNodeInternals(
|
||||
options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
|
||||
options?.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
|
||||
);
|
||||
const [x, y, zoom] = getTransformForBounds(
|
||||
bounds,
|
||||
width,
|
||||
height,
|
||||
options.minZoom ?? minZoom,
|
||||
options.maxZoom ?? maxZoom,
|
||||
options.padding ?? DEFAULT_PADDING
|
||||
options?.minZoom ?? minZoom,
|
||||
options?.maxZoom ?? maxZoom,
|
||||
options?.padding ?? DEFAULT_PADDING
|
||||
);
|
||||
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 nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom;
|
||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
||||
const centerX = width / 2 - x * nextZoom;
|
||||
const centerY = height / 2 - y * nextZoom;
|
||||
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
|
||||
|
||||
d3Zoom.transform(d3Selection, transform);
|
||||
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform);
|
||||
},
|
||||
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
|
||||
fitBounds: (bounds, options) => {
|
||||
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);
|
||||
|
||||
d3Zoom.transform(d3Selection, transform);
|
||||
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform);
|
||||
},
|
||||
project: (position: XYPosition) => {
|
||||
const { transform, snapToGrid, snapGrid } = store.getState();
|
||||
|
||||
Reference in New Issue
Block a user