diff --git a/packages/visx-event/src/typeGuards.ts b/packages/visx-event/src/typeGuards.ts index 9349f141d3..24320328f3 100644 --- a/packages/visx-event/src/typeGuards.ts +++ b/packages/visx-event/src/typeGuards.ts @@ -28,10 +28,19 @@ export function isMouseEvent(event?: EventType): event is MouseEvent { return !!event && 'clientX' in event; } -// functional definition of event -export function isEvent(event?: EventType | Element): event is EventType { +function isNativeEvent(event: any): event is Event { + return event && typeof event === 'object' && 'target' in event && 'currentTarget' in event; +} + +function isReactSyntheticEvent(event: any): event is React.SyntheticEvent { return ( - !!event && - (event instanceof Event || ('nativeEvent' in event && event.nativeEvent instanceof Event)) + event && + typeof event === 'object' && + 'nativeEvent' in event && + isNativeEvent((event as React.SyntheticEvent).nativeEvent) ); } + +export function isEvent(event?: EventType | Element): event is EventType { + return !!event && (isNativeEvent(event) || isReactSyntheticEvent(event)); +} diff --git a/packages/visx-xychart/src/components/Tooltip.tsx b/packages/visx-xychart/src/components/Tooltip.tsx index f5a763d6a7..59fcdb513d 100644 --- a/packages/visx-xychart/src/components/Tooltip.tsx +++ b/packages/visx-xychart/src/components/Tooltip.tsx @@ -93,6 +93,22 @@ function defaultRenderGlyph(props: RenderTooltipGlyphProps return ; } +function getIframeOffsets(target) { + let offsetX = 0; + let offsetY = 0; + let currentWindow = target.ownerDocument.defaultView; + + while (currentWindow && currentWindow.frameElement) { + const frameElement = currentWindow.frameElement; + const frameRect = frameElement.getBoundingClientRect(); + offsetX += frameRect.left; + offsetY += frameRect.top; + currentWindow = currentWindow.parent; + } + + return { x: offsetX, y: offsetY }; +} + function TooltipInner({ debounce, detectBounds, @@ -132,15 +148,46 @@ function TooltipInner({ zIndex, }); + function getIframeOffsets(target) { + let offsetX = 0; + let offsetY = 0; + let currentWindow = target.ownerDocument.defaultView; + + while (currentWindow && currentWindow.frameElement) { + const frameElement = currentWindow.frameElement; + const frameRect = frameElement.getBoundingClientRect(); + offsetX += frameRect.left; + offsetY += frameRect.top; + currentWindow = currentWindow.parent; + } + + return { x: offsetX, y: offsetY }; + } + // To correctly position itself in a Portal, the tooltip must know its container bounds // this is done by rendering an invisible node whose ref can be used to find its parentElement const setContainerRef = useCallback( (ownRef: HTMLElement | SVGElement | null) => { - containerRef(ownRef?.parentElement ?? null); + if (ownRef && ownRef.parentElement instanceof Element) { + const iframeOffsets = getIframeOffsets(ownRef); + const parentRect = ownRef.parentElement.getBoundingClientRect(); + const adjustedContainer = { + ...ownRef.parentElement, + getBoundingClientRect: () => ({ + ...parentRect, + x: parentRect.x + iframeOffsets.x, + y: parentRect.y + iframeOffsets.y, + left: parentRect.left + iframeOffsets.x, + top: parentRect.top + iframeOffsets.y, + }), + }; + containerRef(adjustedContainer); + } else { + containerRef(null); + } }, [containerRef], ); - const tooltipContent = tooltipContext?.tooltipOpen ? renderTooltip({ ...tooltipContext, colorScale }) : null; @@ -301,10 +348,9 @@ function TooltipInner({ )} - {glyphProps.map(({ x, y, ...props }, i) => ( - // We render glyps in a portal so that they can overflow the container if necessary + {glyphProps.map(({ key, x, y, ...props }) => (