refactor(panOnDrag): boolean or numbers for configuration

This commit is contained in:
moklick
2022-12-15 12:43:52 +01:00
parent 72a4f4a5fb
commit dca41aaf0b
4 changed files with 53 additions and 36 deletions
+10 -2
View File
@@ -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;
} }
+25 -20
View File
@@ -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;
+1 -1
View File
@@ -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;