diff --git a/examples/vite-app/src/examples/Figma/index.tsx b/examples/vite-app/src/examples/Figma/index.tsx index 12b867fe..bddc4e0b 100644 --- a/examples/vite-app/src/examples/Figma/index.tsx +++ b/examples/vite-app/src/examples/Figma/index.tsx @@ -6,6 +6,7 @@ import ReactFlow, { Edge, useReactFlow, useKeyPress, + SelectionMode, } from 'reactflow'; const MULTI_SELECT_KEY = ['Meta', 'Shift']; @@ -26,6 +27,8 @@ const defaultEdgeOptions = { zIndex: 0 }; const logEvent = (e: any) => console.log(e); +const onPaneClick = (e: any) => console.log('click pane', e); + const BasicFlow = () => { const instance = useReactFlow(); const spaceBarPressed = useKeyPress('Space'); @@ -60,8 +63,8 @@ const BasicFlow = () => { { className="react-flow-basic-example" minZoom={0.2} maxZoom={4} + onPaneClick={onPaneClick} fitView defaultEdgeOptions={defaultEdgeOptions} selectNodesOnDrag={false} diff --git a/packages/core/src/components/UserSelection/index.tsx b/packages/core/src/components/UserSelection/index.tsx index 38d5a5ba..85b56860 100644 --- a/packages/core/src/components/UserSelection/index.tsx +++ b/packages/core/src/components/UserSelection/index.tsx @@ -2,7 +2,7 @@ * The user selection rectangle gets displayed when a user drags the mouse while pressing shift */ -import { memo, useState, useRef } from 'react'; +import { memo, useState, useRef, MouseEvent as ReactMouseEvent } from 'react'; import shallow from 'zustand/shallow'; import cc from 'classcat'; @@ -10,7 +10,8 @@ import { containerStyle } from '../../styles'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { getSelectionChanges } from '../../utils/changes'; import { getConnectedEdges, getNodesInside } from '../../utils/graph'; -import type { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect, ReactFlowProps } from '../../types'; +import { SelectionMode } from '../../types'; +import type { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types'; type SelectionRect = Rect & { startX: number; @@ -21,19 +22,20 @@ type EventHandlers = { [key: string]: React.MouseEventHandler | React.WheelEvent type UserSelectionProps = { isSelectionMode: boolean; - selectBoxMode?: ReactFlowProps['selectBoxMode']; - onSelectionStart?: (e: React.MouseEvent) => void; - onSelectionEnd?: (e: React.MouseEvent) => void; - onClick?: (e: React.MouseEvent) => void; - onContextMenu?: (e: React.MouseEvent) => void; - onWheel?: (e: React.WheelEvent) => void; - onMouseEnter?: (e: React.MouseEvent) => void; - onMouseMove?: (e: React.MouseEvent) => void; - onMouseLeave?: (e: React.MouseEvent) => void; + 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; }; -function getMousePosition(event: React.MouseEvent, containerBounds: DOMRect): XYPosition { +function getMousePosition(event: ReactMouseEvent, containerBounds: DOMRect): XYPosition { return { x: event.clientX - containerBounds.left, y: event.clientY - containerBounds.top, @@ -44,7 +46,7 @@ const wrapHandler = ( handler: React.MouseEventHandler | undefined, containerRef: React.MutableRefObject ): React.MouseEventHandler => { - return (event: React.MouseEvent) => { + return (event: ReactMouseEvent) => { if (event.target !== containerRef.current) { return; } @@ -67,15 +69,16 @@ const selector = (s: ReactFlowState) => ({ const UserSelection = memo( ({ isSelectionMode, - selectBoxMode = 'Contained', + selectionMode = SelectionMode.Contained, + panOnDrag, onSelectionStart, onSelectionEnd, - onClick, - onContextMenu, - onWheel, - onMouseEnter: onPaneMouseEnter, - onMouseMove: onPaneMouseMove, - onMouseLeave: onPaneMouseLeave, + onPaneClick, + onPaneContextMenu, + onPaneScroll, + onPaneMouseEnter, + onPaneMouseMove, + onPaneMouseLeave, children, }: UserSelectionProps) => { const container = useRef(null); @@ -95,7 +98,24 @@ const UserSelection = memo( prevSelectedEdgesCount.current = 0; }; - const onMouseDown = (event: React.MouseEvent): void => { + const onClick = (event: ReactMouseEvent) => { + onPaneClick?.(event); + store.getState().resetSelectedElements(); + store.setState({ nodesSelectionActive: false }); + }; + + const onContextMenu = (event: ReactMouseEvent) => { + if (panOnDrag === 'RightClick') { + event.preventDefault(); + return; + } + + onPaneContextMenu?.(event); + }; + + const onWheel = onPaneScroll ? (event: React.WheelEvent) => onPaneScroll(event) : undefined; + + const onMouseDown = (event: ReactMouseEvent): void => { if (!elementsSelectable || !isSelectionMode || event.button !== 0 || event.target !== container.current) { return; } @@ -119,7 +139,7 @@ const UserSelection = memo( onSelectionStart?.(event); }; - const onMouseMove = (event: React.MouseEvent): void => { + const onMouseMove = (event: ReactMouseEvent): void => { if (!isSelectionMode || !containerBounds.current || !userSelectionRect) { return; } @@ -144,7 +164,7 @@ const UserSelection = memo( nodeInternals, nextUserSelectRect, transform, - selectBoxMode === 'Overlap', + selectionMode === SelectionMode.Overlap, true, nodeOrigin ); @@ -170,7 +190,7 @@ const UserSelection = memo( setUserSelectionRect(nextUserSelectRect); }; - const onMouseUp = (event: React.MouseEvent) => { + const onMouseUp = (event: ReactMouseEvent) => { // We only want to trigger click functions when in selection mode if // the user did not move the mouse. if (!userSelectionActive && userSelectionRect && event.target === container.current) { @@ -184,7 +204,7 @@ const UserSelection = memo( onSelectionEnd?.(event); }; - const onMouseLeave = (event: React.MouseEvent) => { + const onMouseLeave = (event: ReactMouseEvent) => { if (userSelectionActive) { store.setState({ nodesSelectionActive: prevSelectedNodesCount.current > 0 }); onSelectionEnd?.(event); diff --git a/packages/core/src/container/FlowRenderer/index.tsx b/packages/core/src/container/FlowRenderer/index.tsx index bd31f0d1..1af8da7f 100644 --- a/packages/core/src/container/FlowRenderer/index.tsx +++ b/packages/core/src/container/FlowRenderer/index.tsx @@ -43,8 +43,8 @@ const FlowRenderer = ({ onMoveStart, onMoveEnd, selectionKeyCode, - selectBoxOnDrag, - selectBoxMode, + selectionOnDrag, + selectionMode, onSelectionStart, onSelectionEnd, multiSelectionKeyCode, @@ -67,29 +67,13 @@ const FlowRenderer = ({ noPanClassName, disableKeyboardA11y, }: FlowRendererProps) => { - const store = useStoreApi(); const nodesSelectionActive = useStore(selector); const selectionKeyPressed = useKeyPress(selectionKeyCode); - const isSelectionMode = selectionKeyPressed || (selectBoxOnDrag && panOnDrag !== true); + const isSelectionMode = selectionKeyPressed || (selectionOnDrag && panOnDrag !== true); useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode }); - const onClick = (event: MouseEvent) => { - onPaneClick?.(event); - store.getState().resetSelectedElements(); - store.setState({ nodesSelectionActive: false }); - }; - - const onContextMenu = (event: MouseEvent) => { - if (panOnDrag === 'RightClick') { - event.preventDefault(); - } else { - onPaneContextMenu?.(event); - } - }; - const onWheel = onPaneScroll ? (event: WheelEvent) => onPaneScroll(event) : undefined; - return ( {children} {nodesSelectionActive && ( diff --git a/packages/core/src/container/GraphView/index.tsx b/packages/core/src/container/GraphView/index.tsx index a0cedc43..fac6cab9 100644 --- a/packages/core/src/container/GraphView/index.tsx +++ b/packages/core/src/container/GraphView/index.tsx @@ -58,8 +58,8 @@ const GraphView = ({ connectionLineComponent, connectionLineContainerStyle, selectionKeyCode, - selectBoxOnDrag, - selectBoxMode, + selectionOnDrag, + selectionMode, multiSelectionKeyCode, zoomActivationKeyCode, deleteKeyCode, @@ -114,8 +114,8 @@ const GraphView = ({ onPaneScroll={onPaneScroll} deleteKeyCode={deleteKeyCode} selectionKeyCode={selectionKeyCode} - selectBoxOnDrag={selectBoxOnDrag} - selectBoxMode={selectBoxMode} + selectionOnDrag={selectionOnDrag} + selectionMode={selectionMode} onSelectionStart={onSelectionStart} onSelectionEnd={onSelectionEnd} multiSelectionKeyCode={multiSelectionKeyCode} diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 46e6971c..14cd4d39 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -17,7 +17,7 @@ import GraphView from '../GraphView'; import Wrapper from './Wrapper'; import { infiniteExtent } from '../../store/initialState'; import { useNodeOrEdgeTypes } from './utils'; -import { ConnectionLineType, ConnectionMode, PanOnScrollMode } from '../../types'; +import { ConnectionLineType, ConnectionMode, PanOnScrollMode, SelectionMode } from '../../types'; import type { EdgeTypes, EdgeTypesWrapped, @@ -101,8 +101,8 @@ const ReactFlow = forwardRef( connectionLineContainerStyle, deleteKeyCode = 'Backspace', selectionKeyCode = 'Shift', - selectBoxOnDrag = false, - selectBoxMode = 'Contained', + selectionOnDrag = false, + selectionMode = SelectionMode.Contained, multiSelectionKeyCode = 'Meta', zoomActivationKeyCode = 'Meta', snapToGrid = false, @@ -197,8 +197,8 @@ const ReactFlow = forwardRef( connectionLineComponent={connectionLineComponent} connectionLineContainerStyle={connectionLineContainerStyle} selectionKeyCode={selectionKeyCode} - selectBoxOnDrag={selectBoxOnDrag} - selectBoxMode={selectBoxMode} + selectionOnDrag={selectionOnDrag} + selectionMode={selectionMode} deleteKeyCode={deleteKeyCode} multiSelectionKeyCode={multiSelectionKeyCode} zoomActivationKeyCode={zoomActivationKeyCode} diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index 06feddd0..41d81b7a 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -21,7 +21,7 @@ type ZoomPaneProps = Omit< | 'multiSelectionKeyCode' | 'noDragClassName' | 'disableKeyboardA11y' - | 'selectBoxOnDrag' + | 'selectionOnDrag' >; const viewChanged = (prevViewport: Viewport, eventViewport: any): boolean => diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index a96f006e..551b98bb 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -35,6 +35,7 @@ import type { NodeOrigin, EdgeMouseHandler, HandleType, + SelectionMode, } from '.'; export type ReactFlowProps = HTMLAttributes & { @@ -96,8 +97,8 @@ export type ReactFlowProps = HTMLAttributes & { connectionMode?: ConnectionMode; deleteKeyCode?: KeyCode | null; selectionKeyCode?: KeyCode | null; - selectBoxOnDrag?: boolean; - selectBoxMode?: 'Overlap' | 'Contained'; + selectionOnDrag?: boolean; + selectionMode?: SelectionMode; multiSelectionKeyCode?: KeyCode | null; zoomActivationKeyCode?: KeyCode | null; snapToGrid?: boolean; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index 472df101..ee1e9684 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -3,7 +3,7 @@ import type { MouseEvent as ReactMouseEvent, ComponentType, MemoExoticComponent import type { D3DragEvent, Selection as D3Selection, SubjectPosition, ZoomBehavior } from 'd3'; import type { XYPosition, Rect, Transform, CoordinateExtent } from './utils'; -import type { NodeChange, EdgeChange, NodePositionChange } from './changes'; +import type { NodeChange, EdgeChange } from './changes'; import type { Node, NodeInternals, @@ -248,3 +248,8 @@ export type ProOptions = { }; export type UseDragEvent = D3DragEvent; + +export enum SelectionMode { + Overlap = 'overlap', + Contained = 'contained', +}