diff --git a/.changeset/eight-rice-laugh.md b/.changeset/eight-rice-laugh.md new file mode 100644 index 00000000..dd528c10 --- /dev/null +++ b/.changeset/eight-rice-laugh.md @@ -0,0 +1,5 @@ +--- +'@xyflow/svelte': patch +--- + +Fix showing selection box when no nodes are selected diff --git a/.changeset/sour-jokes-argue.md b/.changeset/sour-jokes-argue.md new file mode 100644 index 00000000..dca49fd6 --- /dev/null +++ b/.changeset/sour-jokes-argue.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Fix onPaneClick events being suppressed when selectionOnDrag=true diff --git a/.changeset/thirty-goats-invent.md b/.changeset/thirty-goats-invent.md new file mode 100644 index 00000000..c57c53f5 --- /dev/null +++ b/.changeset/thirty-goats-invent.md @@ -0,0 +1,5 @@ +--- +'@xyflow/svelte': patch +--- + +Remove focus outline from selection box diff --git a/examples/react/src/examples/Figma/index.tsx b/examples/react/src/examples/Figma/index.tsx index 6780b6cb..2ac0d6b6 100644 --- a/examples/react/src/examples/Figma/index.tsx +++ b/examples/react/src/examples/Figma/index.tsx @@ -30,10 +30,11 @@ const BasicFlow = () => { { onMoveStart={onMoveStart} onMove={onMove} onMoveEnd={onMoveEnd} + onPaneClick={(e) => console.log('pane click', e)} + onSelectionStart={(e) => console.log('on selection start', e)} + onSelectionEnd={(e) => console.log('on selection end', e)} + onPointerDown={(e) => console.log('pointer down', e)} + onPointerUp={(e) => console.log('pointer up', e)} + onClick={(e) => console.log('click', e)} > diff --git a/examples/svelte/src/routes/examples/figma/+page.svelte b/examples/svelte/src/routes/examples/figma/+page.svelte index 35a694ac..9c448353 100644 --- a/examples/svelte/src/routes/examples/figma/+page.svelte +++ b/examples/svelte/src/routes/examples/figma/+page.svelte @@ -9,11 +9,6 @@ import '@xyflow/svelte/dist/style.css'; - const onPaneContextMenu = (e: any) => { - e.preventDefault(); - console.log('context menu'); - }; - const panOnDrag = [1, 2]; const onmovestart = (e: any) => console.log('move start', e); @@ -46,10 +41,14 @@ selectionMode={SelectionMode.Partial} selectionOnDrag panOnScroll + paneClickDistance={100} {panOnDrag} {onmovestart} {onmove} {onmoveend} + onpaneclick={(e) => console.log('on pane click', e)} + onselectionend={(e) => console.log('on selection end', e)} + onselectionstart={(e) => console.log('on selection start', e)} > diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index 73b6000a..7f375d0a 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -69,7 +69,6 @@ const reactFlowFieldsToTrack = [ 'onBeforeDelete', 'debug', 'autoPanSpeed', - 'paneClickDistance', 'ariaLabelConfig', ] as const; @@ -93,7 +92,6 @@ const selector = (s: ReactFlowState) => ({ setNodeExtent: s.setNodeExtent, reset: s.reset, setDefaultNodesAndEdges: s.setDefaultNodesAndEdges, - setPaneClickDistance: s.setPaneClickDistance, }); const initPrevValues = { @@ -109,7 +107,6 @@ const initPrevValues = { elementsSelectable: true, noPanClassName: 'nopan', rfId: '1', - paneClickDistance: 0, }; export function StoreUpdater( @@ -124,7 +121,6 @@ export function StoreUpdater(); @@ -155,7 +151,6 @@ export function StoreUpdater({ onViewportChange={onViewportChange} isControlledViewport={isControlledViewport} paneClickDistance={paneClickDistance} + selectionOnDrag={_selectionOnDrag} > ({ isSelecting={!!isSelecting} selectionMode={selectionMode} selectionKeyPressed={selectionKeyPressed} + paneClickDistance={paneClickDistance} selectionOnDrag={_selectionOnDrag} > {children} diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index ef86be3f..94f23c35 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -1,19 +1,15 @@ import { useRef, + type MouseEventHandler, + type MutableRefObject, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, + type WheelEvent as ReactWheelEvent, type ReactNode, } from 'react'; import { shallow } from 'zustand/shallow'; import cc from 'classcat'; -import { - getNodesInside, - getEventPosition, - SelectionMode, - areSetsEqual, - type NodeChange, - type EdgeChange, -} from '@xyflow/system'; +import { getNodesInside, getEventPosition, SelectionMode, areSetsEqual } from '@xyflow/system'; import { UserSelection } from '../../components/UserSelection'; import { containerStyle } from '../../styles/utils'; @@ -25,6 +21,7 @@ type PaneProps = { isSelecting: boolean; selectionKeyPressed: boolean; children: ReactNode; + paneClickDistance: number; } & Partial< Pick< ReactFlowProps, @@ -43,9 +40,9 @@ type PaneProps = { >; const wrapHandler = ( - handler: React.MouseEventHandler | undefined, - containerRef: React.MutableRefObject -): React.MouseEventHandler => { + handler: MouseEventHandler | undefined, + containerRef: MutableRefObject +): MouseEventHandler => { return (event: ReactMouseEvent) => { if (event.target !== containerRef.current) { return; @@ -66,6 +63,7 @@ export function Pane({ selectionKeyPressed, selectionMode = SelectionMode.Full, panOnDrag, + paneClickDistance, selectionOnDrag, onSelectionStart, onSelectionEnd, @@ -79,17 +77,15 @@ export function Pane({ }: PaneProps) { const store = useStoreApi(); const { userSelectionActive, elementsSelectable, dragging, connectionInProgress } = useStore(selector, shallow); - const hasActiveSelection = elementsSelectable && (isSelecting || userSelectionActive); + const isSelectionEnabled = elementsSelectable && (isSelecting || userSelectionActive); const container = useRef(null); const containerBounds = useRef(); - const selectedNodeIds = useRef>(new Set()); const selectedEdgeIds = useRef>(new Set()); // Used to prevent click events when the user lets go of the selectionKey during a selection const selectionInProgress = useRef(false); - const selectionStarted = useRef(false); const onClick = (event: ReactMouseEvent) => { // We prevent click events when the user let go of the selectionKey during a selection @@ -113,17 +109,13 @@ export function Pane({ onPaneContextMenu?.(event); }; - const onWheel = onPaneScroll ? (event: React.WheelEvent) => onPaneScroll(event) : undefined; + const onWheel = onPaneScroll ? (event: ReactWheelEvent) => onPaneScroll(event) : undefined; const onClickCapture = (event: ReactMouseEvent) => { - const isSelectionOnDragActive = - (selectionOnDrag && container.current === event.target) || !selectionOnDrag || selectionKeyPressed; - - if (!isSelectionOnDragActive) { - return; + if (selectionInProgress.current) { + event.stopPropagation(); + selectionInProgress.current = false; } - - event.stopPropagation(); }; // We are using capture here in order to prevent other pointer events @@ -131,35 +123,23 @@ export function Pane({ const onPointerDownCapture = (event: ReactPointerEvent): void => { const { resetSelectedElements, domNode } = store.getState(); containerBounds.current = domNode?.getBoundingClientRect(); + if (!containerBounds.current) return; - const isNoKeyEvent = event.target !== container.current && !!(event.target as HTMLElement).closest('.nokey'); - const isSelectionActive = - (selectionOnDrag && container.current === event.target) || !selectionOnDrag || selectionKeyPressed; + const eventTargetIsContainer = event.target === container.current; + // if a child element has the 'nokey' class, we don't want to swallow the event and don't start a selection + const isNoKeyEvent = !eventTargetIsContainer && !!(event.target as HTMLElement).closest('.nokey'); + const isSelectionActive = (selectionOnDrag && eventTargetIsContainer) || selectionKeyPressed; - if ( - !elementsSelectable || - !isSelecting || - event.button !== 0 || - !containerBounds.current || - isNoKeyEvent || - !isSelectionActive || - !event.isPrimary - ) { + if (isNoKeyEvent || !isSelecting || !isSelectionActive || event.button !== 0 || !event.isPrimary) { return; } - event.stopPropagation(); - event.preventDefault(); - (event.target as Partial)?.setPointerCapture?.(event.pointerId); - selectionStarted.current = true; selectionInProgress.current = false; const { x, y } = getEventPosition(event.nativeEvent, containerBounds.current); - resetSelectedElements(); - store.setState({ userSelectionRect: { width: 0, @@ -171,7 +151,17 @@ export function Pane({ }, }); - onSelectionStart?.(event); + if (!eventTargetIsContainer) { + event.stopPropagation(); + event.preventDefault(); + } + + if (paneClickDistance === 0 || selectionKeyPressed) { + resetSelectedElements(); + + onSelectionStart?.(event); + selectionInProgress.current = true; + } }; const onPointerMove = (event: ReactPointerEvent): void => { @@ -184,17 +174,32 @@ export function Pane({ triggerNodeChanges, triggerEdgeChanges, defaultEdgeOptions, + resetSelectedElements, } = store.getState(); if (!containerBounds.current || !userSelectionRect) { return; } - selectionInProgress.current = true; - const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current); const { startX, startY } = userSelectionRect; + if ( + !selectionInProgress.current && + event.target === container.current && + !selectionKeyPressed && + paneClickDistance > 0 + ) { + const distance = Math.hypot(mouseX - startX, mouseY - startY); + if (distance <= paneClickDistance) { + return; + } + resetSelectedElements(); + onSelectionStart?.(event); + } + + selectionInProgress.current = true; + const nextUserSelectRect = { startX, startY, @@ -229,12 +234,12 @@ export function Pane({ } if (!areSetsEqual(prevSelectedNodeIds, selectedNodeIds.current)) { - const changes = getSelectionChanges(nodeLookup, selectedNodeIds.current, true) as NodeChange[]; + const changes = getSelectionChanges(nodeLookup, selectedNodeIds.current, true); triggerNodeChanges(changes); } if (!areSetsEqual(prevSelectedEdgeIds, selectedEdgeIds.current)) { - const changes = getSelectionChanges(edgeLookup, selectedEdgeIds.current) as EdgeChange[]; + const changes = getSelectionChanges(edgeLookup, selectedEdgeIds.current); triggerEdgeChanges(changes); } @@ -246,18 +251,17 @@ export function Pane({ }; const onPointerUp = (event: ReactPointerEvent) => { - if (event.button !== 0 || !selectionStarted.current) { + if (event.button !== 0) { return; } (event.target as Partial)?.releasePointerCapture?.(event.pointerId); - const { userSelectionRect } = store.getState(); /* * 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) { + if (!userSelectionActive && event.target === container.current && store.getState().userSelectionRect) { onClick?.(event); } @@ -266,17 +270,10 @@ export function Pane({ userSelectionRect: null, nodesSelectionActive: selectedNodeIds.current.size > 0, }); - onSelectionEnd?.(event); - /* - * If the user kept holding the selectionKey during the selection, - * we need to reset the selectionInProgress, so the next click event is not prevented - */ - if (selectionKeyPressed || selectionOnDrag) { - selectionInProgress.current = false; + if (selectionInProgress.current) { + onSelectionEnd?.(event); } - - selectionStarted.current = false; }; const draggable = panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0)); @@ -284,14 +281,14 @@ export function Pane({ return (
( nodeDragThreshold={nodeDragThreshold} connectionDragThreshold={connectionDragThreshold} onBeforeDelete={onBeforeDelete} - paneClickDistance={paneClickDistance} debug={debug} ariaLabelConfig={ariaLabelConfig} /> diff --git a/packages/react/src/container/ZoomPane/index.tsx b/packages/react/src/container/ZoomPane/index.tsx index bce2b2b7..67babfa2 100644 --- a/packages/react/src/container/ZoomPane/index.tsx +++ b/packages/react/src/container/ZoomPane/index.tsx @@ -12,12 +12,7 @@ import type { ReactFlowState } from '../../types'; type ZoomPaneProps = Omit< FlowRendererProps, - | 'deleteKeyCode' - | 'selectionKeyCode' - | 'multiSelectionKeyCode' - | 'noDragClassName' - | 'disableKeyboardA11y' - | 'selectionOnDrag' + 'deleteKeyCode' | 'selectionKeyCode' | 'multiSelectionKeyCode' | 'noDragClassName' | 'disableKeyboardA11y' > & { isControlledViewport: boolean; }; @@ -49,6 +44,7 @@ export function ZoomPane({ onViewportChange, isControlledViewport, paneClickDistance, + selectionOnDrag, }: ZoomPaneProps) { const store = useStoreApi(); const zoomPane = useRef(null); @@ -77,7 +73,6 @@ export function ZoomPane({ maxZoom, translateExtent, viewport: defaultViewport, - paneClickDistance, onDraggingChange: (paneDragging: boolean) => store.setState({ paneDragging }), onPanZoomStart: (event, vp) => { const { onViewportChangeStart, onMoveStart } = store.getState(); @@ -128,6 +123,8 @@ export function ZoomPane({ lib, onTransformChange, connectionInProgress, + selectionOnDrag, + paneClickDistance, }); }, [ onPaneContextMenu, @@ -146,6 +143,8 @@ export function ZoomPane({ lib, onTransformChange, connectionInProgress, + selectionOnDrag, + paneClickDistance, ]); return ( diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 085f8b1e..3254bdfa 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -312,9 +312,6 @@ const createStore = ({ set({ translateExtent }); }, - setPaneClickDistance: (clickDistance) => { - get().panZoom?.setClickDistance(clickDistance); - }, resetSelectedElements: () => { const { edges, nodes, triggerNodeChanges, triggerEdgeChanges, elementsSelectable } = get(); diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts index 1fe0d308..59a05127 100644 --- a/packages/react/src/types/store.ts +++ b/packages/react/src/types/store.ts @@ -175,7 +175,6 @@ export type ReactFlowActions = { triggerEdgeChanges: (changes: EdgeChange[]) => void; panBy: PanBy; setCenter: SetCenter; - setPaneClickDistance: (distance: number) => void; }; export type ReactFlowState = ReactFlowStore< diff --git a/packages/svelte/src/lib/actions/zoom/index.ts b/packages/svelte/src/lib/actions/zoom/index.ts index 27d7328f..0fd7d72d 100644 --- a/packages/svelte/src/lib/actions/zoom/index.ts +++ b/packages/svelte/src/lib/actions/zoom/index.ts @@ -35,6 +35,7 @@ type ZoomParams = { userSelectionActive: boolean; lib: string; paneClickDistance: number; + selectionOnDrag?: boolean; onTransformChange: (transform: Transform) => void; onDraggingChange: (dragging: boolean) => void; connectionInProgress: boolean; @@ -49,7 +50,6 @@ export default function zoom(domNode: Element, params: ZoomParams) { onPanZoom, onPanZoomEnd, translateExtent, - paneClickDistance, setPanZoomInstance, onDraggingChange, onTransformChange @@ -61,7 +61,6 @@ export default function zoom(domNode: Element, params: ZoomParams) { maxZoom, translateExtent, viewport: initialViewport, - paneClickDistance, onPanZoom, onPanZoomStart, onPanZoomEnd, diff --git a/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte b/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte index 54109028..65e12b6a 100644 --- a/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte +++ b/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte @@ -31,7 +31,12 @@ if (store.selectionRectMode === 'nodes') { // eslint-disable-next-line @typescript-eslint/no-unused-expressions store.nodes; - return getInternalNodesBounds(store.nodeLookup, { filter: (node) => !!node.selected }); + const nodeBounds = getInternalNodesBounds(store.nodeLookup, { + filter: (node) => !!node.selected + }); + if (nodeBounds.width > 0 && nodeBounds.height > 0) { + return nodeBounds; + } } return null; }); @@ -92,4 +97,9 @@ z-index: 2000; pointer-events: all; } + + .svelte-flow__selection-wrapper:focus, + .svelte-flow__selection-wrapper:focus-visible { + outline: none; + } diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte index ef77b1de..d2fdec9d 100644 --- a/packages/svelte/src/lib/container/Pane/Pane.svelte +++ b/packages/svelte/src/lib/container/Pane/Pane.svelte @@ -47,6 +47,7 @@ let { store = $bindable(), panOnDrag = true, + paneClickDistance = 1, selectionOnDrag, onpaneclick, onpanecontextmenu, @@ -67,61 +68,44 @@ let panOnDragActive = $derived(store.panActivationKeyPressed || panOnDrag); let isSelecting = $derived( store.selectionKeyPressed || - store.selectionRect || + !!store.selectionRect || (selectionOnDrag && panOnDragActive !== true) ); - let hasActiveSelection = $derived( + let isSelectionEnabled = $derived( store.elementsSelectable && (isSelecting || store.selectionRectMode === 'user') ); // Used to prevent click events when the user lets go of the selectionKey during a selection let selectionInProgress = false; - function onClick(event: MouseEvent) { - // We prevent click events when the user let go of the selectionKey during a selection - // We also prevent click events when a connection is in progress - if (selectionInProgress || store.connection.inProgress) { - selectionInProgress = false; - return; - } - - onpaneclick?.({ event }); - store.unselectNodesAndEdges(); - store.selectionRectMode = null; - } - // We start the selection process when the user clicks down on the pane function onPointerDownCapture(event: PointerEvent) { containerBounds = container?.getBoundingClientRect(); + if (!containerBounds) return; + + const eventTargetIsContainer = event.target === container; const isNoKeyEvent = - event.target !== container && !!(event.target as HTMLElement).closest('.nokey'); + !eventTargetIsContainer && !!(event.target as HTMLElement).closest('.nokey'); const isSelectionActive = - (selectionOnDrag && container === event.target) || - !selectionOnDrag || - store.selectionKeyPressed; + (selectionOnDrag && eventTargetIsContainer) || store.selectionKeyPressed; if ( - !store.elementsSelectable || - !isSelecting || - event.button !== 0 || - !containerBounds || isNoKeyEvent || + !isSelecting || !isSelectionActive || + event.button !== 0 || !event.isPrimary ) { return; } - event.stopPropagation(); - event.preventDefault(); - (event.target as Partial)?.setPointerCapture?.(event.pointerId); - const { x, y } = getEventPosition(event, containerBounds); + selectionInProgress = false; - store.unselectNodesAndEdges(); + const { x, y } = getEventPosition(event, containerBounds); store.selectionRect = { width: 0, @@ -132,7 +116,17 @@ y }; - onselectionstart?.(event); + if (!eventTargetIsContainer) { + event.stopPropagation(); + event.preventDefault(); + } + + if (paneClickDistance === 0 || store.selectionKeyPressed) { + store.unselectNodesAndEdges(); + + onselectionstart?.(event); + selectionInProgress = true; + } } function onPointerMove(event: PointerEvent) { @@ -140,11 +134,25 @@ return; } - selectionInProgress = true; - const mousePos = getEventPosition(event, containerBounds); const { startX = 0, startY = 0 } = store.selectionRect; + if ( + !selectionInProgress && + event.target === container && + !store.selectionKeyPressed && + paneClickDistance > 0 + ) { + const distance = Math.hypot(mousePos.x - startX, mousePos.y - startY); + if (distance <= paneClickDistance) { + return; + } + store.unselectNodesAndEdges(); + onselectionstart?.(event); + } + + selectionInProgress = true; + const nextUserSelectRect = { ...store.selectionRect, x: mousePos.x < startX ? mousePos.x : startX, @@ -203,22 +211,20 @@ // We only want to trigger click functions when in selection mode if // the user did not move the mouse. - if (!isSelecting && store.selectionRectMode === 'user' && event.target === container) { + + if (!selectionInProgress && event.target === container) { onClick?.(event); } + store.selectionRect = null; - if (selectedNodeIds.size > 0) { - store.selectionRectMode = 'nodes'; + if (selectionInProgress) { + store.selectionRectMode = selectedNodeIds.size > 0 ? 'nodes' : null; } - // If the user kept holding the selectionKey during the selection, - // we need to reset the selectionInProgress, so the next click event is not prevented - if (store.selectionKeyPressed) { - selectionInProgress = false; + if (selectionInProgress) { + onselectionend?.(event); } - - onselectionend?.(event); } const onContextMenu = (event: MouseEvent) => { @@ -231,17 +237,25 @@ }; const onClickCapture = (event: MouseEvent) => { - const isSelectionActive = - (selectionOnDrag && container === event.target) || - !selectionOnDrag || - store.selectionKeyPressed; + if (selectionInProgress) { + event.stopPropagation(); + selectionInProgress = false; + } + }; - if (!isSelectionActive) { + function onClick(event: MouseEvent) { + // We prevent click events when the user let go of the selectionKey during a selection + // We also prevent click events when a connection is in progress + if (selectionInProgress || store.connection.inProgress) { + selectionInProgress = false; return; } - event.stopPropagation(); - }; + onpaneclick?.({ event }); + store.unselectNodesAndEdges(); + store.selectionRectMode = null; + store.selectionRect = null; + } @@ -252,12 +266,12 @@ class:draggable={panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0))} class:dragging={store.dragging} class:selection={isSelecting} - onclick={hasActiveSelection ? undefined : wrapHandler(onClick, container)} - onpointerdowncapture={hasActiveSelection ? onPointerDownCapture : undefined} - onpointermove={hasActiveSelection ? onPointerMove : undefined} - onpointerup={hasActiveSelection ? onPointerUp : undefined} + onclick={isSelectionEnabled ? undefined : wrapHandler(onClick, container)} + onpointerdowncapture={isSelectionEnabled ? onPointerDownCapture : undefined} + onpointermove={isSelectionEnabled ? onPointerMove : undefined} + onpointerup={isSelectionEnabled ? onPointerUp : undefined} oncontextmenu={wrapHandler(onContextMenu, container)} - onclickcapture={hasActiveSelection ? onClickCapture : undefined} + onclickcapture={isSelectionEnabled ? onClickCapture : undefined} > {@render children()}
diff --git a/packages/svelte/src/lib/container/Pane/types.ts b/packages/svelte/src/lib/container/Pane/types.ts index 8442834e..f0cef4df 100644 --- a/packages/svelte/src/lib/container/Pane/types.ts +++ b/packages/svelte/src/lib/container/Pane/types.ts @@ -5,6 +5,7 @@ import type { SvelteFlowStore } from '$lib/store/types'; export type PaneProps = { store: SvelteFlowStore; panOnDrag?: boolean | number[]; + paneClickDistance: number; selectionOnDrag?: boolean; onselectionstart?: (event: PointerEvent) => void; onselectionend?: (event: PointerEvent) => void; diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte index d16a3890..d92ff2d6 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte @@ -156,6 +156,7 @@ {panOnScrollSpeed} {panOnDrag} {paneClickDistance} + {selectionOnDrag} {onmovestart} {onmove} {onmoveend} @@ -168,6 +169,7 @@ {onselectionstart} {onselectionend} {panOnDrag} + {paneClickDistance} {selectionOnDrag} > diff --git a/packages/svelte/src/lib/container/Zoom/Zoom.svelte b/packages/svelte/src/lib/container/Zoom/Zoom.svelte index 70971e46..761cffef 100644 --- a/packages/svelte/src/lib/container/Zoom/Zoom.svelte +++ b/packages/svelte/src/lib/container/Zoom/Zoom.svelte @@ -16,6 +16,7 @@ panOnScroll, panOnScrollSpeed, paneClickDistance, + selectionOnDrag, onmovestart, onmove, onmoveend, @@ -69,6 +70,7 @@ translateExtent: store.translateExtent, lib: 'svelte', paneClickDistance, + selectionOnDrag, onTransformChange: (transform: Transform) => { store.viewport = { x: transform[0], y: transform[1], zoom: transform[2] }; }, diff --git a/packages/svelte/src/lib/container/Zoom/types.ts b/packages/svelte/src/lib/container/Zoom/types.ts index 8573fbcb..b9e5bf93 100644 --- a/packages/svelte/src/lib/container/Zoom/types.ts +++ b/packages/svelte/src/lib/container/Zoom/types.ts @@ -14,6 +14,7 @@ export type ZoomProps( elements: T[], elementsToDeselect: Set | null = null @@ -393,7 +389,6 @@ export function createStore void; setMaxZoom: (maxZoom: number) => void; setTranslateExtent: (extent: CoordinateExtent) => void; - setPaneClickDistance: (distance: number) => void; fitView: (options?: FitViewOptions) => Promise; setCenter: SetCenter; updateNodePositions: UpdateNodePositions; diff --git a/packages/system/src/types/panzoom.ts b/packages/system/src/types/panzoom.ts index 7fa54e97..11291868 100644 --- a/packages/system/src/types/panzoom.ts +++ b/packages/system/src/types/panzoom.ts @@ -9,7 +9,6 @@ export type PanZoomParams = { domNode: Element; minZoom: number; maxZoom: number; - paneClickDistance: number; viewport: Viewport; translateExtent: CoordinateExtent; onDraggingChange: OnDraggingChange; @@ -43,6 +42,8 @@ export type PanZoomUpdateOptions = { lib: string; onTransformChange: OnTransformChange; connectionInProgress: boolean; + paneClickDistance: number; + selectionOnDrag?: boolean; }; export type PanZoomInstance = { diff --git a/packages/system/src/xypanzoom/XYPanZoom.ts b/packages/system/src/xypanzoom/XYPanZoom.ts index 85b8ba1c..ee1a763e 100644 --- a/packages/system/src/xypanzoom/XYPanZoom.ts +++ b/packages/system/src/xypanzoom/XYPanZoom.ts @@ -37,7 +37,6 @@ export function XYPanZoom({ domNode, minZoom, maxZoom, - paneClickDistance, translateExtent, viewport, onPanZoom, @@ -55,10 +54,7 @@ export function XYPanZoom({ isPanScrolling: false, }; const bbox = domNode.getBoundingClientRect(); - const d3ZoomInstance = zoom() - .clickDistance(!isNumeric(paneClickDistance) || paneClickDistance < 0 ? 0 : paneClickDistance) - .scaleExtent([minZoom, maxZoom]) - .translateExtent(translateExtent); + const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent); const d3Selection = select(domNode).call(d3ZoomInstance); setViewportConstrained( @@ -109,6 +105,8 @@ export function XYPanZoom({ lib, onTransformChange, connectionInProgress, + paneClickDistance, + selectionOnDrag, }: PanZoomUpdateOptions) { if (userSelectionActive && !zoomPanValues.isZoomingOrPanning) { destroy(); @@ -116,6 +114,10 @@ export function XYPanZoom({ const isPanOnScroll = panOnScroll && !zoomActivationKeyPressed && !userSelectionActive; + d3ZoomInstance.clickDistance( + selectionOnDrag ? Infinity : !isNumeric(paneClickDistance) || paneClickDistance < 0 ? 0 : paneClickDistance + ); + const wheelHandler = isPanOnScroll ? createPanOnScrollHandler({ zoomPanValues,