From dca41aaf0b5036b19ef5b3458a61aa01ef6db697 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 15 Dec 2022 12:43:52 +0100 Subject: [PATCH] refactor(panOnDrag): boolean or numbers for configuration --- .../vite-app/src/examples/Figma/index.tsx | 12 ++++- .../src/components/UserSelection/index.tsx | 30 +++++++------ .../core/src/container/ZoomPane/index.tsx | 45 ++++++++++--------- packages/core/src/types/component-props.ts | 2 +- 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/examples/vite-app/src/examples/Figma/index.tsx b/examples/vite-app/src/examples/Figma/index.tsx index 0ac44198..e1e20dc7 100644 --- a/examples/vite-app/src/examples/Figma/index.tsx +++ b/examples/vite-app/src/examples/Figma/index.tsx @@ -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']; @@ -14,6 +14,13 @@ const initialEdges: Edge[] = [ { id: 'e1-3', source: '1', target: '3' }, ]; +const onPaneContextMenu = (e: any) => { + e.preventDefault(); + console.log('context menu'); +}; + +const panOnDrag = [1, 2]; + const BasicFlow = () => { return ( { defaultEdges={initialEdges} selectionOnDrag selectionMode={SelectionMode.Partial} - panOnDrag="RightClick" + panOnDrag={panOnDrag} panOnScroll zoomActivationKeyCode="Meta" multiSelectionKeyCode={MULTI_SELECT_KEY} + onPaneContextMenu={onPaneContextMenu} fitView selectNodesOnDrag={false} > diff --git a/packages/core/src/components/UserSelection/index.tsx b/packages/core/src/components/UserSelection/index.tsx index 87f801d0..26ba621c 100644 --- a/packages/core/src/components/UserSelection/index.tsx +++ b/packages/core/src/components/UserSelection/index.tsx @@ -11,23 +11,27 @@ import { containerStyle } from '../../styles'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { getSelectionChanges } from '../../utils/changes'; import { getConnectedEdges, getNodesInside } from '../../utils/graph'; -import { SelectionMode } from '../../types'; +import { ReactFlowProps, SelectionMode } from '../../types'; import type { XYPosition, ReactFlowState, NodeChange, EdgeChange } from '../../types'; type UserSelectionProps = { 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; -}; +} & Partial< + Pick< + ReactFlowProps, + | 'selectionMode' + | 'panOnDrag' + | 'onSelectionStart' + | 'onSelectionEnd' + | 'onPaneClick' + | 'onPaneContextMenu' + | 'onPaneScroll' + | 'onPaneMouseEnter' + | 'onPaneMouseMove' + | 'onPaneMouseLeave' + > +>; function getMousePosition(event: ReactMouseEvent, containerBounds: DOMRect): XYPosition { return { @@ -90,7 +94,7 @@ const UserSelection = memo( }; const onContextMenu = (event: ReactMouseEvent) => { - if (panOnDrag === 'RightClick') { + if (Array.isArray(panOnDrag) && panOnDrag?.includes(2)) { event.preventDefault(); return; } diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index 9dcd946f..2099d9a1 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -35,6 +35,9 @@ const eventToFlowTransform = (eventViewport: any): Viewport => ({ 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) => ({ d3Zoom: s.d3Zoom, d3Selection: s.d3Selection, @@ -68,7 +71,7 @@ const ZoomPane = ({ const timerId = useRef>(); const store = useStoreApi(); const isZoomingOrPanning = useRef(false); - const hasMouseMoved = useRef(false); + const zoomedWithRightMouseButton = useRef(false); const zoomPane = useRef(null); const prevTransform = useRef({ x: 0, y: 0, zoom: 0 }); const { d3Zoom, d3Selection, d3ZoomHandler, userSelectionActive } = useStore(selector, shallow); @@ -170,15 +173,16 @@ const ZoomPane = ({ 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); } - if (panOnDrag === 'RightClick' && onPaneContextMenu) { - hasMouseMoved.current = true; - } }); } } @@ -193,6 +197,7 @@ const ZoomPane = ({ const { onViewportChangeStart } = store.getState(); isZoomingOrPanning.current = true; + if (event.sourceEvent?.type === 'mousedown') { store.setState({ paneDragging: true }); } @@ -219,6 +224,15 @@ const ZoomPane = ({ isZoomingOrPanning.current = 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)) { const flowTransform = eventToFlowTransform(event.transform); prevTransform.current = flowTransform; @@ -232,19 +246,9 @@ const ZoomPane = ({ 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(() => { if (d3Zoom) { @@ -299,17 +303,18 @@ const ZoomPane = ({ 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 ( - panOnDrag === 'RightClick' && - (event.type === 'mousedown' || event.type === 'touchstart') && - event.button !== 2 + Array.isArray(panOnDrag) && + !panOnDrag.includes(event.button) && + (event.type === 'mousedown' || event.type === 'touchstart') ) { return false; } // 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 return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed; diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index d0caf183..68b2cdab 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -113,7 +113,7 @@ export type ReactFlowProps = HTMLAttributes & { initNodeOrigin?: NodeOrigin; elementsSelectable?: boolean; selectNodesOnDrag?: boolean; - panOnDrag?: boolean | 'RightClick'; + panOnDrag?: boolean | number[]; minZoom?: number; maxZoom?: number; defaultViewport?: Viewport;