From 3bf6e3dc37036d0633284a060af6c496b90159ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Mar 2025 10:26:59 +0100 Subject: [PATCH] Dashboards: Fix issues with panel selection and dragging (#102000) --- .../components/PanelChrome/PanelChrome.tsx | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index b629dbeee83..310ae7fe7b4 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import { CSSProperties, PointerEvent, ReactElement, ReactNode, useId, useRef, useState } from 'react'; +import { CSSProperties, ReactElement, ReactNode, useId, useRef, useState } from 'react'; import * as React from 'react'; import { useMeasure, useToggle } from 'react-use'; @@ -143,7 +143,6 @@ export function PanelChrome({ onFocus, onMouseMove, onMouseEnter, - onDragStart, showMenuAlways = false, }: PanelChromeProps) { const theme = useTheme2(); @@ -151,7 +150,7 @@ export function PanelChrome({ const panelContentId = useId(); const panelTitleId = useId().replace(/:/g, '_'); const { isSelected, onSelect, isSelectable } = useElementSelection(selectionId); - const pointerDownEvt = useRef(null); + const pointerDownPos = useRef<{ screenX: number; screenY: number }>({ screenX: 0, screenY: 0 }); const hasHeader = !hoverHeader; @@ -196,6 +195,33 @@ export function PanelChrome({ const testid = typeof title === 'string' ? selectors.components.Panels.Panel.title(title) : 'Panel'; + // Handle drag & selection events + // Mainly the tricky bit of differentiating between dragging and selecting + + const onPointerUp = (evt: React.PointerEvent) => { + evt.stopPropagation(); + + const distance = Math.sqrt( + Math.pow(pointerDownPos.current.screenX - evt.screenX, 2) + + Math.pow(pointerDownPos.current.screenY - evt.screenY, 2) + ); + + // If we are dragging some distance or clicking on elements that should cancel dragging (panel menu, etc) + if ( + distance > 10 || + (dragClassCancel && evt.target instanceof HTMLElement && evt.target.closest(`.${dragClassCancel}`)) + ) { + return; + } + + onSelect?.(evt); + }; + + const onPointerDown = (evt: React.PointerEvent) => { + evt.stopPropagation(); + pointerDownPos.current = { screenX: evt.screenX, screenY: evt.screenY }; + }; + const headerContent = ( <> {/* Non collapsible title */} @@ -321,30 +347,10 @@ export function PanelChrome({ className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container" - onPointerDown={(evt) => { - evt.stopPropagation(); - pointerDownEvt.current = evt; - }} - onPointerMove={() => { - if (pointerDownEvt.current) { - onDragStart?.(pointerDownEvt.current); - pointerDownEvt.current = null; - } - }} + onPointerDown={onPointerDown} onMouseEnter={isSelectable ? onHeaderEnter : undefined} onMouseLeave={isSelectable ? onHeaderLeave : undefined} - onPointerUp={(evt) => { - evt.stopPropagation(); - if ( - pointerDownEvt.current && - dragClassCancel && - evt.target instanceof HTMLElement && - !evt.target.closest(`.${dragClassCancel}`) - ) { - onSelect?.(pointerDownEvt.current); - pointerDownEvt.current = null; - } - }} + onPointerUp={onPointerUp} > {statusMessage && (