Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,21 @@ const CustomPopover: React.FC<Props> = ({
}
};

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};

if (isOpen) {
updatePosition();
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleKeyDown);
window.addEventListener('scroll', updatePosition);
window.addEventListener('resize', updatePosition);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('scroll', updatePosition);
window.removeEventListener('resize', updatePosition);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import { safeHtmlSpan } from '@superset-ui/core';
import { styled } from '@apache-superset/core/theme';
import { ReactNode } from 'react';
import { ReactNode, useEffect, useRef, useState } from 'react';

export type TooltipProps = {
tooltip:
Expand All @@ -43,6 +43,12 @@ const StyledDiv = styled.div<{
top: ${top}px;
left: ${left}px;
z-index: 9;
/* deck.gl tooltips track the cursor across the canvas — capturing pointer
events here would block layer hover/click and create flicker loops as
the cursor enters and leaves the floating tooltip. Dismissal is via the
Escape key handler below; WCAG 1.4.13 "hoverable" is satisfied because
the tooltip remains visible under the cursor while pointing at the
feature. */
pointer-events: none;
${
variant === 'default'
Expand All @@ -66,7 +72,39 @@ const StyledDiv = styled.div<{

export default function Tooltip(props: TooltipProps) {
const { tooltip, variant = 'default' } = props;
if (typeof tooltip === 'undefined' || tooltip === null) {
const [dismissed, setDismissed] = useState(false);
const wasVisibleRef = useRef(false);

// Reset dismissed when the tooltip transitions from hidden to visible. This
// handles the case where the cursor leaves the chart and re-enters a
// different pickable that happens to render the same content — content
// alone wouldn't trigger a reset there.
useEffect(() => {
const isVisible = !!tooltip;
if (isVisible && !wasVisibleRef.current) {
setDismissed(false);
}
wasVisibleRef.current = isVisible;
}, [tooltip]);

// Reset on content change too (most common new-target signal).
useEffect(() => {
setDismissed(false);
}, [tooltip?.content]);

// Bind the Escape listener once per visibility cycle. Depending on
// `tooltip` directly would re-bind on every cursor move (x/y change).
const visible = !!tooltip && !dismissed;
useEffect(() => {
if (!visible) return undefined;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setDismissed(true);
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [visible]);

if (!tooltip || dismissed) {
return null;
}

Expand Down
10 changes: 9 additions & 1 deletion superset-frontend/src/features/tasks/TaskPayloadPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { useState } from 'react';
import { useState, useEffect } from 'react';
import { styled } from '@apache-superset/core/theme';
import { Popover } from '@superset-ui/core/components';
import { Icons } from '@superset-ui/core/components/Icons';
Expand Down Expand Up @@ -54,6 +54,14 @@ export default function TaskPayloadPopover({
}: TaskPayloadPopoverProps) {
const [visible, setVisible] = useState(false);

useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setVisible(false);
};
if (visible) document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [visible]);

const content = (
<PayloadContainer>
<PayloadPre>{JSON.stringify(payload, null, 2)}</PayloadPre>
Expand Down
10 changes: 9 additions & 1 deletion superset-frontend/src/features/tasks/TaskStackTracePopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { useState, useCallback } from 'react';
import { useState, useCallback, useEffect } from 'react';
import { t } from '@apache-superset/core/translation';
import { styled } from '@apache-superset/core/theme';
import { Popover, Tooltip } from '@superset-ui/core/components';
Expand Down Expand Up @@ -90,6 +90,14 @@ export default function TaskStackTracePopover({
const [copied, setCopied] = useState(false);
const { addDangerToast } = useToasts();

useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setVisible(false);
};
if (visible) document.addEventListener('keydown', handleKeyDown);
Comment on lines +93 to +97
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions setting ECharts tooltips to enterable: true and adding ESC handlers on chart containers, but these changes don't appear in this diff (e.g. no enterable: true usage found in the frontend). Either update the PR description to match what's actually included, or add the missing ECharts/chart-container changes in this PR.

Copilot uses AI. Check for mistakes.
return () => document.removeEventListener('keydown', handleKeyDown);
}, [visible]);

const handleCopy = useCallback(() => {
copyTextToClipboard(() => Promise.resolve(stackTrace))
.then(() => {
Expand Down
Loading