refactor(panOnDrag): boolean or numbers for configuration
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import ReactFlow, { Background, BackgroundVariant, Node, Edge, SelectionMode, Viewport } from 'reactflow';
|
import ReactFlow, { Background, BackgroundVariant, Node, Edge, SelectionMode } from 'reactflow';
|
||||||
|
|
||||||
const MULTI_SELECT_KEY = ['Meta', 'Shift'];
|
const MULTI_SELECT_KEY = ['Meta', 'Shift'];
|
||||||
|
|
||||||
@@ -14,6 +14,13 @@ const initialEdges: Edge[] = [
|
|||||||
{ id: 'e1-3', source: '1', target: '3' },
|
{ id: 'e1-3', source: '1', target: '3' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const onPaneContextMenu = (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('context menu');
|
||||||
|
};
|
||||||
|
|
||||||
|
const panOnDrag = [1, 2];
|
||||||
|
|
||||||
const BasicFlow = () => {
|
const BasicFlow = () => {
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
@@ -21,10 +28,11 @@ const BasicFlow = () => {
|
|||||||
defaultEdges={initialEdges}
|
defaultEdges={initialEdges}
|
||||||
selectionOnDrag
|
selectionOnDrag
|
||||||
selectionMode={SelectionMode.Partial}
|
selectionMode={SelectionMode.Partial}
|
||||||
panOnDrag="RightClick"
|
panOnDrag={panOnDrag}
|
||||||
panOnScroll
|
panOnScroll
|
||||||
zoomActivationKeyCode="Meta"
|
zoomActivationKeyCode="Meta"
|
||||||
multiSelectionKeyCode={MULTI_SELECT_KEY}
|
multiSelectionKeyCode={MULTI_SELECT_KEY}
|
||||||
|
onPaneContextMenu={onPaneContextMenu}
|
||||||
fitView
|
fitView
|
||||||
selectNodesOnDrag={false}
|
selectNodesOnDrag={false}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,23 +11,27 @@ import { containerStyle } from '../../styles';
|
|||||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||||
import { getSelectionChanges } from '../../utils/changes';
|
import { getSelectionChanges } from '../../utils/changes';
|
||||||
import { getConnectedEdges, getNodesInside } from '../../utils/graph';
|
import { getConnectedEdges, getNodesInside } from '../../utils/graph';
|
||||||
import { SelectionMode } from '../../types';
|
import { ReactFlowProps, SelectionMode } from '../../types';
|
||||||
import type { XYPosition, ReactFlowState, NodeChange, EdgeChange } from '../../types';
|
import type { XYPosition, ReactFlowState, NodeChange, EdgeChange } from '../../types';
|
||||||
|
|
||||||
type UserSelectionProps = {
|
type UserSelectionProps = {
|
||||||
isSelecting: boolean;
|
isSelecting: boolean;
|
||||||
selectionMode?: SelectionMode;
|
|
||||||
panOnDrag?: boolean | 'RightClick';
|
|
||||||
onSelectionStart?: (e: ReactMouseEvent) => void;
|
|
||||||
onSelectionEnd?: (e: ReactMouseEvent) => void;
|
|
||||||
onPaneClick?: (e: ReactMouseEvent) => void;
|
|
||||||
onPaneContextMenu?: (e: ReactMouseEvent) => void;
|
|
||||||
onPaneScroll?: (e: React.WheelEvent) => void;
|
|
||||||
onPaneMouseEnter?: (e: ReactMouseEvent) => void;
|
|
||||||
onPaneMouseMove?: (e: ReactMouseEvent) => void;
|
|
||||||
onPaneMouseLeave?: (e: ReactMouseEvent) => void;
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
} & Partial<
|
||||||
|
Pick<
|
||||||
|
ReactFlowProps,
|
||||||
|
| 'selectionMode'
|
||||||
|
| 'panOnDrag'
|
||||||
|
| 'onSelectionStart'
|
||||||
|
| 'onSelectionEnd'
|
||||||
|
| 'onPaneClick'
|
||||||
|
| 'onPaneContextMenu'
|
||||||
|
| 'onPaneScroll'
|
||||||
|
| 'onPaneMouseEnter'
|
||||||
|
| 'onPaneMouseMove'
|
||||||
|
| 'onPaneMouseLeave'
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
function getMousePosition(event: ReactMouseEvent, containerBounds: DOMRect): XYPosition {
|
function getMousePosition(event: ReactMouseEvent, containerBounds: DOMRect): XYPosition {
|
||||||
return {
|
return {
|
||||||
@@ -90,7 +94,7 @@ const UserSelection = memo(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onContextMenu = (event: ReactMouseEvent) => {
|
const onContextMenu = (event: ReactMouseEvent) => {
|
||||||
if (panOnDrag === 'RightClick') {
|
if (Array.isArray(panOnDrag) && panOnDrag?.includes(2)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ const eventToFlowTransform = (eventViewport: any): Viewport => ({
|
|||||||
|
|
||||||
const isWrappedWithClass = (event: any, className: string | undefined) => event.target.closest(`.${className}`);
|
const isWrappedWithClass = (event: any, className: string | undefined) => event.target.closest(`.${className}`);
|
||||||
|
|
||||||
|
const isRightClickPan = (panOnDrag: FlowRendererProps['panOnDrag'], usedButton: number) =>
|
||||||
|
usedButton === 2 && Array.isArray(panOnDrag) && panOnDrag.includes(2);
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
d3Zoom: s.d3Zoom,
|
d3Zoom: s.d3Zoom,
|
||||||
d3Selection: s.d3Selection,
|
d3Selection: s.d3Selection,
|
||||||
@@ -68,7 +71,7 @@ const ZoomPane = ({
|
|||||||
const timerId = useRef<ReturnType<typeof setTimeout>>();
|
const timerId = useRef<ReturnType<typeof setTimeout>>();
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const isZoomingOrPanning = useRef(false);
|
const isZoomingOrPanning = useRef(false);
|
||||||
const hasMouseMoved = useRef(false);
|
const zoomedWithRightMouseButton = useRef(false);
|
||||||
const zoomPane = useRef<HTMLDivElement>(null);
|
const zoomPane = useRef<HTMLDivElement>(null);
|
||||||
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);
|
||||||
@@ -170,15 +173,16 @@ const ZoomPane = ({
|
|||||||
const { onViewportChange } = store.getState();
|
const { onViewportChange } = store.getState();
|
||||||
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
||||||
|
|
||||||
|
zoomedWithRightMouseButton.current = !!(
|
||||||
|
onPaneContextMenu && isRightClickPan(panOnDrag, event.sourceEvent?.button)
|
||||||
|
);
|
||||||
|
|
||||||
if (onMove || onViewportChange) {
|
if (onMove || onViewportChange) {
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
|
|
||||||
onViewportChange?.(flowTransform);
|
onViewportChange?.(flowTransform);
|
||||||
onMove?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
onMove?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
|
||||||
}
|
}
|
||||||
if (panOnDrag === 'RightClick' && onPaneContextMenu) {
|
|
||||||
hasMouseMoved.current = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,6 +197,7 @@ const ZoomPane = ({
|
|||||||
|
|
||||||
const { onViewportChangeStart } = store.getState();
|
const { onViewportChangeStart } = store.getState();
|
||||||
isZoomingOrPanning.current = true;
|
isZoomingOrPanning.current = true;
|
||||||
|
|
||||||
if (event.sourceEvent?.type === 'mousedown') {
|
if (event.sourceEvent?.type === 'mousedown') {
|
||||||
store.setState({ paneDragging: true });
|
store.setState({ paneDragging: true });
|
||||||
}
|
}
|
||||||
@@ -219,6 +224,15 @@ const ZoomPane = ({
|
|||||||
isZoomingOrPanning.current = false;
|
isZoomingOrPanning.current = false;
|
||||||
store.setState({ paneDragging: false });
|
store.setState({ paneDragging: false });
|
||||||
|
|
||||||
|
if (
|
||||||
|
onPaneContextMenu &&
|
||||||
|
isRightClickPan(panOnDrag, event.sourceEvent?.button) &&
|
||||||
|
!zoomedWithRightMouseButton.current
|
||||||
|
) {
|
||||||
|
onPaneContextMenu(event.sourceEvent);
|
||||||
|
}
|
||||||
|
zoomedWithRightMouseButton.current = false;
|
||||||
|
|
||||||
if ((onMoveEnd || onViewportChangeEnd) && viewChanged(prevTransform.current, event.transform)) {
|
if ((onMoveEnd || onViewportChangeEnd) && viewChanged(prevTransform.current, event.transform)) {
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
prevTransform.current = flowTransform;
|
prevTransform.current = flowTransform;
|
||||||
@@ -232,19 +246,9 @@ const ZoomPane = ({
|
|||||||
panOnScroll ? 150 : 0
|
panOnScroll ? 150 : 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
panOnDrag === 'RightClick' &&
|
|
||||||
onPaneContextMenu &&
|
|
||||||
!hasMouseMoved.current &&
|
|
||||||
event.sourceEvent?.button === 2
|
|
||||||
) {
|
|
||||||
onPaneContextMenu(event.sourceEvent);
|
|
||||||
}
|
|
||||||
hasMouseMoved.current = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [d3Zoom, onMoveEnd, panOnScroll, panOnDrag, onPaneContextMenu]);
|
}, [d3Zoom, panOnScroll, panOnDrag, onMoveEnd, onPaneContextMenu]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (d3Zoom) {
|
if (d3Zoom) {
|
||||||
@@ -299,17 +303,18 @@ const ZoomPane = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the pane is only movable using right clicks, prevent all other clicks
|
// if the pane is only movable using allowed clicks
|
||||||
if (
|
if (
|
||||||
panOnDrag === 'RightClick' &&
|
Array.isArray(panOnDrag) &&
|
||||||
(event.type === 'mousedown' || event.type === 'touchstart') &&
|
!panOnDrag.includes(event.button) &&
|
||||||
event.button !== 2
|
(event.type === 'mousedown' || event.type === 'touchstart')
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only allow right clicks if pan on drag is set to right click
|
// We only allow right clicks if pan on drag is set to right click
|
||||||
const buttonAllowed = panOnDrag === 'RightClick' ? 1 !== event.button : !event.button || event.button <= 1;
|
const buttonAllowed =
|
||||||
|
(Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) || !event.button || event.button <= 1;
|
||||||
|
|
||||||
// default filter for d3-zoom
|
// default filter for d3-zoom
|
||||||
return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed;
|
return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed;
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
initNodeOrigin?: NodeOrigin;
|
initNodeOrigin?: NodeOrigin;
|
||||||
elementsSelectable?: boolean;
|
elementsSelectable?: boolean;
|
||||||
selectNodesOnDrag?: boolean;
|
selectNodesOnDrag?: boolean;
|
||||||
panOnDrag?: boolean | 'RightClick';
|
panOnDrag?: boolean | number[];
|
||||||
minZoom?: number;
|
minZoom?: number;
|
||||||
maxZoom?: number;
|
maxZoom?: number;
|
||||||
defaultViewport?: Viewport;
|
defaultViewport?: Viewport;
|
||||||
|
|||||||
Reference in New Issue
Block a user