1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import {useLayoutEffect, useRef} from 'react';
    
  11. 
    
  12. const TOOLTIP_OFFSET_BOTTOM = 10;
    
  13. const TOOLTIP_OFFSET_TOP = 5;
    
  14. 
    
  15. export default function useSmartTooltip({
    
  16.   canvasRef,
    
  17.   mouseX,
    
  18.   mouseY,
    
  19. }: {
    
  20.   canvasRef: {current: HTMLCanvasElement | null},
    
  21.   mouseX: number,
    
  22.   mouseY: number,
    
  23. }): {current: HTMLElement | null} {
    
  24.   const ref = useRef<HTMLElement | null>(null);
    
  25. 
    
  26.   // HACK: Browser extension reports window.innerHeight of 0,
    
  27.   // so we fallback to using the tooltip target element.
    
  28.   let height = window.innerHeight;
    
  29.   let width = window.innerWidth;
    
  30.   const target = canvasRef.current;
    
  31.   if (target !== null) {
    
  32.     const rect = target.getBoundingClientRect();
    
  33.     height = rect.top + rect.height;
    
  34.     width = rect.left + rect.width;
    
  35.   }
    
  36. 
    
  37.   useLayoutEffect(() => {
    
  38.     const element = ref.current;
    
  39.     if (element !== null) {
    
  40.       // Let's check the vertical position.
    
  41.       if (mouseY + TOOLTIP_OFFSET_BOTTOM + element.offsetHeight >= height) {
    
  42.         // The tooltip doesn't fit below the mouse cursor (which is our
    
  43.         // default strategy). Therefore we try to position it either above the
    
  44.         // mouse cursor or finally aligned with the window's top edge.
    
  45.         if (mouseY - TOOLTIP_OFFSET_TOP - element.offsetHeight > 0) {
    
  46.           // We position the tooltip above the mouse cursor if it fits there.
    
  47.           element.style.top = `${
    
  48.             mouseY - element.offsetHeight - TOOLTIP_OFFSET_TOP
    
  49.           }px`;
    
  50.         } else {
    
  51.           // Otherwise we align the tooltip with the window's top edge.
    
  52.           element.style.top = '0px';
    
  53.         }
    
  54.       } else {
    
  55.         element.style.top = `${mouseY + TOOLTIP_OFFSET_BOTTOM}px`;
    
  56.       }
    
  57. 
    
  58.       // Now let's check the horizontal position.
    
  59.       if (mouseX + TOOLTIP_OFFSET_BOTTOM + element.offsetWidth >= width) {
    
  60.         // The tooltip doesn't fit at the right of the mouse cursor (which is
    
  61.         // our default strategy). Therefore we try to position it either at the
    
  62.         // left of the mouse cursor or finally aligned with the window's left
    
  63.         // edge.
    
  64.         if (mouseX - TOOLTIP_OFFSET_TOP - element.offsetWidth > 0) {
    
  65.           // We position the tooltip at the left of the mouse cursor if it fits
    
  66.           // there.
    
  67.           element.style.left = `${
    
  68.             mouseX - element.offsetWidth - TOOLTIP_OFFSET_TOP
    
  69.           }px`;
    
  70.         } else {
    
  71.           // Otherwise, align the tooltip with the window's left edge.
    
  72.           element.style.left = '0px';
    
  73.         }
    
  74.       } else {
    
  75.         element.style.left = `${mouseX + TOOLTIP_OFFSET_BOTTOM}px`;
    
  76.       }
    
  77.     }
    
  78.   });
    
  79. 
    
  80.   return ref;
    
  81. }