import { css , html , LitElement , TemplateResult } from "lit" ; import { customElement , state , property } from 'lit/decorators.js' ; import { smallBreakPoint } from "../utils/css/breakpoints" ; /**
 * A tooltip that can receive focus or contain clickable elements.
 * Usage:
 *  <hover-tooltip anchor="my-button-id" text="hello, world" link="https://test.com"></hover-tooltip>
 */ @ customElement ( 'hover-tooltip' ) export class HoverTooltip extends LitElement { @ property ( ) anchor : string = '' ; @ property ( { type : String } ) text = '' ; @ property ( { type : String } ) link = '' ; @ state ( ) tooltipVisible = false ; @ state ( ) tooltipLocation : [ number , number ] = [ 0 , 0 ] ; anchorElement : HTMLElement | null | undefined = null ; tooltipContainsFocus = false ; tooltipContainsHover = false ; anchorContainsFocus = false ; anchorContainsHover = false ; hideTooltipTimeoutHandle = 0 ; mouseEnterListener = ( ) => this . anchorMouseEntered ( ) ; focusInListner = ( ) => this . anchorFocused ( ) ; mouseLeaveListener = ( ) => this . anchorMouseLeave ( ) ; focusOutListener = ( ) => this . anchorBlurred ( ) ; static get styles ( ) { return css `
      .tooltip-dialog {
        position: absolute;
        margin: 0px;
        box-shadow: 0 0 5px gray;
        background-color: #000;
        opacity: 0;
        transform: translate(10px, 5px); /* shift it down and to the right a bit */
        transition: opacity 0.2s ease-in-out;
        visibility: collapse;
        border-radius: 0.25rem;
        z-index: 1000;
        width: max-content;
        max-width: 250px;
      }

      .tooltip-dialog.show {
        opacity: 0.8;
        pointer-events: auto;
        visibility: visible;
      }

      .tooltip-inner {
        position: relative;
        color: #fff;
        font-size: 14px;
        font-weight: normal;
        padding: 0px 15px 0px 15px;
        line-height: 21px;
      }

      ${ smallBreakPoint ( css `
          .tooltip-inner {
            max-width: 200px;
          }
        ` ) }

      a, a:hover, a:active {
        color: white;
        display: block;
        margin-bottom: 15px;
      }
    ` ; } constructor ( ) { super ( ) ; } connectedCallback ( ) { super . connectedCallback ( ) ; if ( ! this . anchor ) { console . error ( "Attempted to use a <hover-tooltip>, but didn't set the anchor. It should be set to the ID of the element the tooltip is for." ) ; return ; } this . anchorElement = this . parentNode ?. querySelector ( `#${ this . anchor }` ) ; if ( this . anchorElement ) { this . anchorElement . addEventListener ( "mouseenter" , this . mouseEnterListener , { passive : true } ) ; this . anchorElement . addEventListener ( "mouseleave" , this . mouseLeaveListener , { passive : true } ) ; this . anchorElement . addEventListener ( "focusin" , this . focusInListner , { passive : true } ) ; this . anchorElement . addEventListener ( "focusout" , this . focusOutListener , { passive : true } ) ; } } disconnectedCallback ( ) { super . disconnectedCallback ( ) ; if ( this . anchorElement ) { this . anchorElement . removeEventListener ( "mouseenter" , this . mouseEnterListener ) ; this . anchorElement . removeEventListener ( "mouseleave" , this . focusInListner ) ; this . anchorElement . removeEventListener ( "focusin" , this . mouseEnterListener ) ; this . anchorElement . removeEventListener ( "focusout" , this . focusOutListener ) ; } clearTimeout ( this . hideTooltipTimeoutHandle ) ; } showTooltip ( ) { if ( this . anchorElement ) { let scrollOffset = 0 ; const isInModal = ! ! this . getAbsoluteOrFixedOffsetParent ( this . anchorElement ) ; if ( ! isInModal ) { scrollOffset = window . scrollY ; } const boundingRect = this . anchorElement . getBoundingClientRect ( ) ; const [ parentOffsetLeft , parentOffsetTop ] = this . getCumulativeOffset ( this . anchorElement ) ; const tooltipX = boundingRect . top + scrollOffset - parentOffsetTop ; const tooltipY = boundingRect . right - parentOffsetLeft ; this . tooltipLocation = [ tooltipX , tooltipY ] ; this . tooltipVisible = true ; } } getCumulativeOffset ( element ) { let top = 0 ; let left = 0 ; let parent = element . offsetParent as HTMLElement ; while ( parent != null ) { top += parent . offsetTop ; left += parent . offsetLeft ; parent = parent . offsetParent as HTMLElement ; } return [ left , top ] ; } getAbsoluteOrFixedOffsetParent ( el ) { if ( ! el ) { return null ; } let parent = el . offsetParent as HTMLElement ; while ( parent != null && parent !== document . body ) { const parentPositionStyle = window . getComputedStyle ( parent , null ) . position ; if ( parent . style && ( parentPositionStyle === "absolute" || parentPositionStyle === 'fixed' ) ) { return parent ; } parent = parent . offsetParent as HTMLElement ; } return null ; } hideTooltipIfNecessary ( ) { const checkIfShouldBeHidden = ( ) => { this . tooltipVisible = this . anchorContainsFocus || this . anchorContainsHover || this . tooltipContainsFocus || this . tooltipContainsHover ; } ; clearTimeout ( this . hideTooltipTimeoutHandle ) ; this . hideTooltipTimeoutHandle = window . setTimeout ( ( ) => checkIfShouldBeHidden ( ) , 350 ) ; } anchorMouseEntered ( ) { this . anchorContainsHover = true ; this . showTooltip ( ) ; } anchorMouseLeave ( ) { this . anchorContainsHover = false ; this . hideTooltipIfNecessary ( ) ; } anchorFocused ( ) { this . anchorContainsFocus = true ; this . showTooltip ( ) ; } anchorBlurred ( ) { this . anchorContainsFocus = false ; this . hideTooltipIfNecessary ( ) ; } tooltipMouseEntered ( ) { this . tooltipContainsHover = true ; } tooltipMouseLeave ( ) { this . tooltipContainsHover = false ; this . hideTooltipIfNecessary ( ) ; } tooltipFocused ( ) { this . tooltipContainsFocus = true ; } tooltipBlurred ( ) { this . tooltipContainsFocus = false ; this . hideTooltipIfNecessary ( ) ; } render ( ) { const tooltipClass = this . tooltipVisible ? 'tooltip-dialog show' : 'tooltip-dialog' ; return html `
      <div class="${ tooltipClass }" 
        style="top: ${ this . tooltipLocation [ 0 ] }px; left: ${ this . tooltipLocation [ 1 ] }px;"
        @mouseover="${ this . tooltipMouseEntered }"
        @mouseleave="${ this . tooltipMouseLeave }"
        @focusin="${ this . tooltipFocused }"
        @focusout="${ this . tooltipBlurred }">
        <div class="tooltip-inner">
          <p>${ this . text }</p>
          ${ this . renderLink ( ) }
        </div>
      </div>
    ` ; } renderLink ( ) { if ( ! this . link ) { return html `` ; } return html `
      <a target="_blank" href="${ this . link }">
        Read more...
      </a>
    ` ; } }