diff --git a/src/internal/components/chart-legend/index.tsx b/src/internal/components/chart-legend/index.tsx index f9fb07c4..2f47f130 100644 --- a/src/internal/components/chart-legend/index.tsx +++ b/src/internal/components/chart-legend/index.tsx @@ -6,6 +6,7 @@ import clsx from "clsx"; import { circleIndex, + getAllFocusables, handleKey, KeyCode, SingleTabStopNavigationAPI, @@ -70,6 +71,7 @@ export const ChartLegend = ({ const elementsByIndexRef = useRef>([]); const elementsByIdRef = useRef>({}); const tooltipRef = useRef(null); + const tooltipWrapperRef = useRef(null); const highlightControl = useMemo(() => new DebouncedCall(), []); const scrollIntoViewControl = useMemo(() => new DebouncedCall(), []); const [selectedIndex, setSelectedIndex] = useState(0); @@ -103,7 +105,8 @@ export const ChartLegend = ({ return () => { document.removeEventListener("keydown", onDocumentKeyDown, true); }; - }, [items, tooltipItemId, hideTooltip]); + }, [tooltipItemId, hideTooltip]); + const isMouseInContainer = useRef(false); // Scrolling to the highlighted legend item. @@ -147,8 +150,13 @@ export const ChartLegend = ({ function onBlur(event: React.FocusEvent) { navigationAPI.current!.updateFocusTarget(); - // Hide tooltip and clear highlight unless focus moves inside tooltip; - if (tooltipRef.current && event.relatedTarget && !tooltipRef.current.contains(event.relatedTarget)) { + // Hide tooltip and clear highlight unless focus moves inside the tooltip wrapper + // (which contains the tooltip itself and its entry/exit tab traps). + const next = event.relatedTarget as Node | null; + if (next && tooltipWrapperRef.current?.contains(next)) { + return; + } + if (next) { clearHighlight(); hideTooltip(); } @@ -217,25 +225,25 @@ export const ChartLegend = ({ const tooltipPosition = isVertical ? "left" : "bottom"; return ( - getNextFocusTarget()} - onUnregisterActive={(element: HTMLElement) => onUnregisterActive(element, navigationAPI)} +
(isMouseInContainer.current = true)} + onMouseLeave={() => (isMouseInContainer.current = false)} > -
(isMouseInContainer.current = true)} - onMouseLeave={() => (isMouseInContainer.current = false)} + {legendTitle && ( + + {legendTitle} + + )} + getNextFocusTarget()} + onUnregisterActive={(element: HTMLElement) => onUnregisterActive(element, navigationAPI)} > - {legendTitle && ( - - {legendTitle} - - )}
- {tooltipContent && ( + + {tooltipContent && ( +
+ {/* [1] skips FocusLock's leading TabTrap to target the dismiss button. */} +
tooltipRef.current && getAllFocusables(tooltipRef.current)[1]?.focus()} /> {}} + dismissButton={true} + disableDismissAutoFocus={true} + onDismiss={() => { + hideTooltip(true); + elementsByIdRef.current[tooltipTarget.id]?.focus(); + }} position={tooltipPosition} title={tooltipContent.header} onMouseEnter={() => showTooltip(tooltipTarget.id)} onMouseLeave={() => hideTooltip()} - onBlur={() => hideTooltip()} footer={ tooltipContent.footer && ( <> @@ -330,9 +346,10 @@ export const ChartLegend = ({ > {tooltipContent.body} - )} -
- +
tooltipRef.current && getAllFocusables(tooltipRef.current)[1]?.focus()} /> +
+ )} +
); };