diff --git a/packages/grafana-ui/src/components/PanelChrome/HoverWidget.tsx b/packages/grafana-ui/src/components/PanelChrome/HoverWidget.tsx index f7d737934ab..0d22c26a13a 100644 --- a/packages/grafana-ui/src/components/PanelChrome/HoverWidget.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/HoverWidget.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import React, { ReactElement, useCallback, useRef, useState } from 'react'; +import React, { ReactElement, useCallback, useRef } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; @@ -31,18 +31,12 @@ export function HoverWidget({ menu, title, dragClass, children, offset = -32, on draggableRef.current?.releasePointerCapture(e.pointerId); }, []); - const [menuOpen, setMenuOpen] = useState(false); - if (children === undefined || React.Children.count(children) === 0) { return null; } return ( -
+
{dragClass && (
)} @@ -72,10 +65,6 @@ export function HoverWidget({ menu, title, dragClass, children, offset = -32, on function getStyles(theme: GrafanaTheme2) { return { - hidden: css({ - visibility: 'hidden', - opacity: '0', - }), container: css({ label: 'hover-container-widget', transition: `all .1s linear`, diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index f3765c73a8e..152cf4946d3 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -5,6 +5,7 @@ import { GrafanaTheme2, LoadingState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { useStyles2, useTheme2 } from '../../themes'; +import { getFocusStyles } from '../../themes/mixins'; import { DelayRender } from '../../utils/DelayRender'; import { Icon } from '../Icon/Icon'; import { LoadingBar } from '../LoadingBar/LoadingBar'; @@ -157,7 +158,9 @@ export function PanelChrome({ ); return ( -
+ // tabIndex={0} is needed for keyboard accessibility in the plot area + // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex +
{loadingState === LoadingState.Loading ? : null}
@@ -262,20 +265,21 @@ const getStyles = (theme: GrafanaTheme2) => { '.show-on-hover': { opacity: '0', + visibility: 'hidden', }, '&:focus-visible, &:hover': { // only show menu icon on hover or focused panel '.show-on-hover': { opacity: '1', + visibility: 'visible', }, }, - '&:focus-visible': { - outline: `1px solid ${theme.colors.action.focus}`, - }, + '&:focus-visible': getFocusStyles(theme), - '&:focus-within': { + // The not:(:focus) clause is so that this rule is only applied when decendants are focused (important otherwise the hover header is visible when panel is clicked). + '&:focus-within:not(:focus)': { '.show-on-hover': { visibility: 'visible', opacity: '1', diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelMenu.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelMenu.tsx index 2f80058db33..595bda416e0 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelMenu.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelMenu.tsx @@ -14,7 +14,6 @@ interface PanelMenuProps { title?: string; placement?: TooltipPlacement; offset?: [number, number]; - onVisibleChange?: (state: boolean) => void; onOpenMenu?: () => void; } @@ -25,7 +24,6 @@ export function PanelMenu({ offset, dragClassCancel, menuButtonClass, - onVisibleChange, onOpenMenu, }: PanelMenuProps) { const testId = title ? selectors.components.Panels.Panel.menu(title) : `panel-menu-button`; @@ -35,9 +33,8 @@ export function PanelMenu({ if (show && onOpenMenu) { onOpenMenu(); } - return onVisibleChange; }, - [onOpenMenu, onVisibleChange] + [onOpenMenu] ); return ( diff --git a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx index d1ce27ef82e..b18a687db41 100644 --- a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx +++ b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx @@ -42,9 +42,7 @@ export const VizLayout: VizLayoutComponentType = ({ width, height, legend, child if (!legend) { return ( <> - {/* tabIndex={0} is needed for keyboard accessibility in the plot area */} - {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} -
+
{children(width, height)}
@@ -97,11 +95,7 @@ export const VizLayout: VizLayoutComponentType = ({ width, height, legend, child return (
- {/* tabIndex={0} is needed for keyboard accessibility in the plot area */} - {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} -
- {size && children(size.width, size.height)} -
+
{size && children(size.width, size.height)}
{legend}
diff --git a/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx index 0f8a1a1d893..f23cded4172 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx @@ -13,12 +13,12 @@ const SHIFT_MULTIPLIER = 2 as const; const KNOWN_KEYS = new Set(['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Shift', ' ']); const initHook = (u: uPlot) => { - let vizLayoutViz: HTMLElement | null = u.root.closest('[tabindex]'); + let parentWithFocus: HTMLElement | null = u.root.closest('[tabindex]'); let pressedKeys = new Set(); let dragStartX: number | null = null; let keysLastHandledAt: number | null = null; - if (!vizLayoutViz) { + if (!parentWithFocus) { return; } @@ -133,7 +133,7 @@ const initHook = (u: uPlot) => { const onFocus = () => { // We only want to initialize the cursor if the user is using keyboard controls - if (!vizLayoutViz?.matches(':focus-visible')) { + if (!parentWithFocus?.matches(':focus-visible')) { return; } @@ -150,18 +150,17 @@ const initHook = (u: uPlot) => { u.setSelect({ left: 0, top: 0, width: 0, height: 0 }, false); }; - vizLayoutViz.addEventListener('keydown', onKeyDown); - vizLayoutViz.addEventListener('keyup', onKeyUp); - vizLayoutViz.addEventListener('focus', onFocus); - vizLayoutViz.addEventListener('blur', onBlur); + parentWithFocus.addEventListener('keydown', onKeyDown); + parentWithFocus.addEventListener('keyup', onKeyUp); + parentWithFocus.addEventListener('focus', onFocus); + parentWithFocus.addEventListener('blur', onBlur); const onDestroy = () => { - vizLayoutViz?.removeEventListener('keydown', onKeyDown); - vizLayoutViz?.removeEventListener('keyup', onKeyUp); - vizLayoutViz?.removeEventListener('focus', onFocus); - vizLayoutViz?.removeEventListener('blur', onBlur); - - vizLayoutViz = null; + parentWithFocus?.removeEventListener('keydown', onKeyDown); + parentWithFocus?.removeEventListener('keyup', onKeyUp); + parentWithFocus?.removeEventListener('focus', onFocus); + parentWithFocus?.removeEventListener('blur', onBlur); + parentWithFocus = null; }; (u.hooks.destroy ??= []).push(onDestroy); diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx index 6e6e2a06713..76e4da995eb 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx @@ -57,6 +57,7 @@ export const TooltipPlugin = ({ const [coords, setCoords] = useState(null); const [isActive, setIsActive] = useState(false); const isMounted = useMountedState(); + let parentWithFocus: HTMLElement | null = null; const pluginId = `TooltipPlugin`; @@ -92,12 +93,16 @@ export const TooltipPlugin = ({ config.addHook('init', (u) => { plotInstance.current = u; - u.root.parentElement?.addEventListener('focus', plotEnter); u.over.addEventListener('mouseenter', plotEnter); - - u.root.parentElement?.addEventListener('blur', plotLeave); u.over.addEventListener('mouseleave', plotLeave); + parentWithFocus = u.root.closest('[tabindex]'); + + if (parentWithFocus) { + parentWithFocus.addEventListener('focus', plotEnter); + parentWithFocus.addEventListener('blur', plotLeave); + } + if (sync && sync() === DashboardCursorSync.Crosshair) { u.root.classList.add('shared-crosshair'); } @@ -162,11 +167,15 @@ export const TooltipPlugin = ({ return () => { setCoords(null); + if (plotInstance.current) { plotInstance.current.over.removeEventListener('mouseleave', plotLeave); plotInstance.current.over.removeEventListener('mouseenter', plotEnter); - plotInstance.current.root.parentElement?.removeEventListener('focus', plotEnter); - plotInstance.current.root.parentElement?.removeEventListener('blur', plotLeave); + + if (parentWithFocus) { + parentWithFocus.removeEventListener('focus', plotEnter); + parentWithFocus.removeEventListener('blur', plotLeave); + } } }; }, [config, setCoords, setIsActive, setFocusedPointIdx, setFocusedPointIdxs]); diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 6a77ad4eee4..295483aefa7 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -93,6 +93,7 @@ export class DashboardGrid extends PureComponent { } this.props.dashboard.sortPanelsByGridPos(); + this.forceUpdate(); }; triggerForceUpdate = () => {