license import { AnimationFrame } from '@material/animation/animationframe' ; import { getCorrectPropertyName } from '@material/animation/util' ; import { MDCFoundation } from '@material/base/foundation' ; import { EventType , SpecificEventListener } from '@material/base/types' ; import { KEY , normalizeKey } from '@material/dom/keyboard' ; import { MDCTooltipAdapter } from './adapter' ; import { AnchorBoundaryType , attributes , CssClasses , numbers , PositionWithCaret , strings , XPosition , XPositionWithCaret , YPosition , YPositionWithCaret } from './constants' ; const { RICH , SHOWN , SHOWING , SHOWING_TRANSITION , HIDE , HIDE_TRANSITION , MULTILINE_TOOLTIP } = CssClasses ; enum AnimationKeys { POLL_ANCHOR = 'poll_anchor' } const HAS_WINDOW = typeof window !== 'undefined' ; export class MDCTooltipFoundation extends MDCFoundation < MDCTooltipAdapter > { static override get defaultAdapter ( ) : MDCTooltipAdapter { return { getAttribute : ( ) => null , setAttribute : ( ) => undefined , removeAttribute : ( ) => undefined , addClass : ( ) => undefined , hasClass : ( ) => false , removeClass : ( ) => undefined , getComputedStyleProperty : ( ) => '' , setStyleProperty : ( ) => undefined , setSurfaceAnimationStyleProperty : ( ) => undefined , getViewportWidth : ( ) => 0 , getViewportHeight : ( ) => 0 , getTooltipSize : ( ) => ( { width : 0 , height : 0 } ) , getAnchorBoundingRect : ( ) => ( { top : 0 , right : 0 , bottom : 0 , left : 0 , width : 0 , height : 0 } as any ) , getParentBoundingRect : ( ) => ( { top : 0 , right : 0 , bottom : 0 , left : 0 , width : 0 , height : 0 } as any ) , getAnchorAttribute : ( ) => null , setAnchorAttribute : ( ) => null , isRTL : ( ) => false , anchorContainsElement : ( ) => false , tooltipContainsElement : ( ) => false , focusAnchorElement : ( ) => undefined , registerEventHandler : ( ) => undefined , deregisterEventHandler : ( ) => undefined , registerAnchorEventHandler : ( ) => undefined , deregisterAnchorEventHandler : ( ) => undefined , registerDocumentEventHandler : ( ) => undefined , deregisterDocumentEventHandler : ( ) => undefined , registerWindowEventHandler : ( ) => undefined , deregisterWindowEventHandler : ( ) => undefined , notifyHidden : ( ) => undefined , getTooltipCaretBoundingRect : ( ) => ( { top : 0 , right : 0 , bottom : 0 , left : 0 , width : 0 , height : 0 } as any ) , setTooltipCaretStyle : ( ) => undefined , clearTooltipCaretStyles : ( ) => undefined , getActiveElement : ( ) => null , } ; } private interactiveTooltip ! : boolean ; private richTooltip ! : boolean ; private persistentTooltip ! : boolean ; private hasCaret ! : boolean ; private tooltipShown = false ; private anchorGap = numbers . BOUNDED_ANCHOR_GAP ; private xTooltipPos = XPosition . DETECTED ; private yTooltipPos = YPosition . DETECTED ; private tooltipPositionWithCaret = PositionWithCaret . DETECTED ; private readonly minViewportTooltipThreshold = numbers . MIN_VIEWPORT_TOOLTIP_THRESHOLD ; private hideDelayMs = numbers . HIDE_DELAY_MS ; private showDelayMs = numbers . SHOW_DELAY_MS ; private anchorRect : ClientRect | null = null ; private parentRect : ClientRect | null = null ; private frameId : number | null = null ; private hideTimeout : number | null = null ; private showTimeout : number | null = null ; private readonly animFrame : AnimationFrame ; private readonly anchorBlurHandler : SpecificEventListener < 'blur' > ; private readonly documentClickHandler : SpecificEventListener < 'click' > ; private readonly documentKeydownHandler : SpecificEventListener < 'keydown' > ; private readonly tooltipMouseEnterHandler : SpecificEventListener < 'mouseenter' > ; private readonly tooltipMouseLeaveHandler : SpecificEventListener < 'mouseleave' > ; private readonly richTooltipFocusOutHandler : SpecificEventListener < 'focusout' > ; private readonly windowScrollHandler : SpecificEventListener < 'scroll' > ; private readonly windowResizeHandler : SpecificEventListener < 'resize' > ; private readonly addAncestorScrollEventListeners = new Array < ( ) => void > ( ) ; private readonly removeAncestorScrollEventListeners = new Array < ( ) => void > ( ) ; constructor ( adapter ? ) { super ( { ... MDCTooltipFoundation . defaultAdapter , ... adapter } ) ; this . animFrame = new AnimationFrame ( ) ; this . anchorBlurHandler = ( evt ) => { this . handleAnchorBlur ( evt ) ; } ; this . documentClickHandler = ( evt ) => { this . handleDocumentClick ( evt ) ; } ; this . documentKeydownHandler = ( evt ) => { this . handleKeydown ( evt ) ; } ; this . tooltipMouseEnterHandler = ( ) => { this . handleTooltipMouseEnter ( ) ; } ; this . tooltipMouseLeaveHandler = ( ) => { this . handleTooltipMouseLeave ( ) ; } ; this . richTooltipFocusOutHandler = ( evt ) => { this . handleRichTooltipFocusOut ( evt ) ; } ; this . windowScrollHandler = ( ) => { this . handleWindowScrollEvent ( ) ; } ; this . windowResizeHandler = ( ) => { this . handleWindowChangeEvent ( ) ; } ; } override init ( ) { this . richTooltip = this . adapter . hasClass ( RICH ) ; this . persistentTooltip = this . adapter . getAttribute ( attributes . PERSISTENT ) === 'true' ; this . interactiveTooltip = ! ! this . adapter . getAnchorAttribute ( attributes . ARIA_EXPANDED ) && this . adapter . getAnchorAttribute ( attributes . ARIA_HASPOPUP ) === 'dialog' ; this . hasCaret = this . richTooltip && this . adapter . getAttribute ( attributes . HAS_CARET ) === 'true' ; } isShown ( ) { return this . tooltipShown ; } isRich ( ) { return this . richTooltip ; } isPersistent ( ) { return this . persistentTooltip ; } handleAnchorMouseEnter ( ) { if ( this . tooltipShown ) { this . show ( ) ; } else { this . clearHideTimeout ( ) ; this . showTimeout = setTimeout ( ( ) => { this . show ( ) ; } , this . showDelayMs ) ; } } handleAnchorTouchstart ( ) { this . showTimeout = setTimeout ( ( ) => { this . show ( ) ; } , this . showDelayMs ) ; this . adapter . registerWindowEventHandler ( 'contextmenu' , this . preventContextMenuOnLongTouch ) ; } private preventContextMenuOnLongTouch ( evt ) { evt . preventDefault ( ) ; } handleAnchorTouchend ( ) { this . clearShowTimeout ( ) ; if ( ! this . isShown ( ) ) { this . adapter . deregisterWindowEventHandler ( 'contextmenu' , this . preventContextMenuOnLongTouch ) ; } } handleAnchorFocus ( evt ) { const { relatedTarget } = evt ; const tooltipContainsRelatedTarget = relatedTarget instanceof HTMLElement && this . adapter . tooltipContainsElement ( relatedTarget ) ; if ( tooltipContainsRelatedTarget ) { return ; } this . showTimeout = setTimeout ( ( ) => { this . show ( ) ; } , this . showDelayMs ) ; } handleAnchorMouseLeave ( ) { this . clearShowTimeout ( ) ; this . hideTimeout = setTimeout ( ( ) => { this . hide ( ) ; } , this . hideDelayMs ) ; } handleAnchorClick ( ) { if ( this . tooltipShown ) { this . hide ( ) ; } else { this . show ( ) ; } } handleDocumentClick ( evt ) { const anchorOrTooltipContainsTargetElement = evt . target instanceof HTMLElement && ( this . adapter . anchorContainsElement ( evt . target ) || this . adapter . tooltipContainsElement ( evt . target ) ) ; if ( this . richTooltip && this . persistentTooltip && anchorOrTooltipContainsTargetElement ) { return ; } this . hide ( ) ; } handleKeydown ( evt ) { const key = normalizeKey ( evt ) ; if ( key === KEY . ESCAPE ) { const activeElement = this . adapter . getActiveElement ( ) ; const tooltipContainsActiveElement = activeElement instanceof HTMLElement && this . adapter . tooltipContainsElement ( activeElement ) ; if ( tooltipContainsActiveElement ) { this . adapter . focusAnchorElement ( ) ; } this . hide ( ) ; } } private handleAnchorBlur ( evt ) { if ( this . richTooltip ) { const tooltipContainsRelatedTargetElement = evt . relatedTarget instanceof HTMLElement && this . adapter . tooltipContainsElement ( evt . relatedTarget ) ; if ( tooltipContainsRelatedTargetElement ) { return ; } if ( evt . relatedTarget === null && this . interactiveTooltip ) { return ; } } this . hide ( ) ; } private handleTooltipMouseEnter ( ) { this . show ( ) ; } private handleTooltipMouseLeave ( ) { this . clearShowTimeout ( ) ; this . hideTimeout = setTimeout ( ( ) => { this . hide ( ) ; } , this . hideDelayMs ) ; } private handleRichTooltipFocusOut ( evt ) { const anchorOrTooltipContainsRelatedTargetElement = evt . relatedTarget instanceof HTMLElement && ( this . adapter . anchorContainsElement ( evt . relatedTarget ) || this . adapter . tooltipContainsElement ( evt . relatedTarget ) ) ; if ( anchorOrTooltipContainsRelatedTargetElement ) { return ; } if ( evt . relatedTarget === null && this . interactiveTooltip ) { return ; } this . hide ( ) ; } private handleWindowScrollEvent ( ) { if ( this . persistentTooltip ) { this . handleWindowChangeEvent ( ) ; return ; } this . hide ( ) ; } /**
     * On window resize or scroll, check the anchor position and size and
     * repostion tooltip if necessary.
     */ private handleWindowChangeEvent ( ) { this . animFrame . request ( AnimationKeys . POLL_ANCHOR , ( ) => { this . repositionTooltipOnAnchorMove ( ) ; } ) ; } show ( ) { this . clearHideTimeout ( ) ; this . clearShowTimeout ( ) ; if ( this . tooltipShown ) { return ; } this . tooltipShown = true ; this . adapter . removeAttribute ( 'aria-hidden' ) ; if ( this . richTooltip ) { if ( this . interactiveTooltip ) { this . adapter . setAnchorAttribute ( 'aria-expanded' , 'true' ) ; } this . adapter . registerEventHandler ( 'focusout' , this . richTooltipFocusOutHandler ) ; } if ( ! this . persistentTooltip ) { this . adapter . registerEventHandler ( 'mouseenter' , this . tooltipMouseEnterHandler ) ; this . adapter . registerEventHandler ( 'mouseleave' , this . tooltipMouseLeaveHandler ) ; } this . adapter . removeClass ( HIDE ) ; this . adapter . addClass ( SHOWING ) ; if ( this . isTooltipMultiline ( ) && ! this . richTooltip ) { this . adapter . addClass ( MULTILINE_TOOLTIP ) ; } this . anchorRect = this . adapter . getAnchorBoundingRect ( ) ; this . parentRect = this . adapter . getParentBoundingRect ( ) ; this . richTooltip ? this . positionRichTooltip ( ) : this . positionPlainTooltip ( ) ; this . adapter . registerAnchorEventHandler ( 'blur' , this . anchorBlurHandler ) ; this . adapter . registerDocumentEventHandler ( 'click' , this . documentClickHandler ) ; this . adapter . registerDocumentEventHandler ( 'keydown' , this . documentKeydownHandler ) ; this . adapter . registerWindowEventHandler ( 'scroll' , this . windowScrollHandler ) ; this . adapter . registerWindowEventHandler ( 'resize' , this . windowResizeHandler ) ; for ( const fn of this . addAncestorScrollEventListeners ) { fn ( ) ; } this . frameId = requestAnimationFrame ( ( ) => { this . clearAllAnimationClasses ( ) ; this . adapter . addClass ( SHOWN ) ; this . adapter . addClass ( SHOWING_TRANSITION ) ; } ) ; } hide ( ) { this . clearHideTimeout ( ) ; this . clearShowTimeout ( ) ; if ( ! this . tooltipShown ) { return ; } if ( this . frameId ) { cancelAnimationFrame ( this . frameId ) ; } this . tooltipShown = false ; this . adapter . setAttribute ( 'aria-hidden' , 'true' ) ; this . adapter . deregisterEventHandler ( 'focusout' , this . richTooltipFocusOutHandler ) ; if ( this . richTooltip ) { if ( this . interactiveTooltip ) { this . adapter . setAnchorAttribute ( 'aria-expanded' , 'false' ) ; } } if ( ! this . persistentTooltip ) { this . adapter . deregisterEventHandler ( 'mouseenter' , this . tooltipMouseEnterHandler ) ; this . adapter . deregisterEventHandler ( 'mouseleave' , this . tooltipMouseLeaveHandler ) ; } this . clearAllAnimationClasses ( ) ; this . adapter . addClass ( HIDE ) ; this . adapter . addClass ( HIDE_TRANSITION ) ; this . adapter . removeClass ( SHOWN ) ; this . adapter . deregisterAnchorEventHandler ( 'blur' , this . anchorBlurHandler ) ; this . adapter . deregisterDocumentEventHandler ( 'click' , this . documentClickHandler ) ; this . adapter . deregisterDocumentEventHandler ( 'keydown' , this . documentKeydownHandler ) ; this . adapter . deregisterWindowEventHandler ( 'scroll' , this . windowScrollHandler ) ; this . adapter . deregisterWindowEventHandler ( 'resize' , this . windowResizeHandler ) ; this . adapter . deregisterWindowEventHandler ( 'contextmenu' , this . preventContextMenuOnLongTouch ) ; for ( const fn of this . removeAncestorScrollEventListeners ) { fn ( ) ; } } handleTransitionEnd ( ) { const isHidingTooltip = this . adapter . hasClass ( HIDE ) ; this . adapter . removeClass ( SHOWING ) ; this . adapter . removeClass ( SHOWING_TRANSITION ) ; this . adapter . removeClass ( HIDE ) ; this . adapter . removeClass ( HIDE_TRANSITION ) ; if ( isHidingTooltip && this . showTimeout === null ) { this . adapter . notifyHidden ( ) ; } } private clearAllAnimationClasses ( ) { this . adapter . removeClass ( SHOWING_TRANSITION ) ; this . adapter . removeClass ( HIDE_TRANSITION ) ; } setTooltipPosition ( position ) { const { xPos , yPos , withCaretPos } = position ; if ( this . hasCaret && withCaretPos ) { this . tooltipPositionWithCaret = withCaretPos ; return ; } if ( xPos ) { this . xTooltipPos = xPos ; } if ( yPos ) { this . yTooltipPos = yPos ; } } setAnchorBoundaryType ( type ) { if ( type === AnchorBoundaryType . UNBOUNDED ) { this . anchorGap = numbers . UNBOUNDED_ANCHOR_GAP ; } else { this . anchorGap = numbers . BOUNDED_ANCHOR_GAP ; } } setShowDelay ( delayMs ) { this . showDelayMs = delayMs ; } setHideDelay ( delayMs ) { this . hideDelayMs = delayMs ; } private isTooltipMultiline ( ) { const tooltipSize = this . adapter . getTooltipSize ( ) ; return tooltipSize . height > numbers . MIN_HEIGHT && tooltipSize . width >= numbers . MAX_WIDTH ; } private positionPlainTooltip ( ) { const { top , yTransformOrigin , left , xTransformOrigin } = this . calculateTooltipStyles ( this . anchorRect ) ; const transformProperty = HAS_WINDOW ? getCorrectPropertyName ( window , 'transform' ) : 'transform' ; this . adapter . setSurfaceAnimationStyleProperty ( `${ transformProperty }-origin` , `${ xTransformOrigin } ${ yTransformOrigin }` ) ; this . adapter . setStyleProperty ( 'top' , `${ top }px` ) ; this . adapter . setStyleProperty ( 'left' , `${ left }px` ) ; } private positionRichTooltip ( ) { const width = this . adapter . getComputedStyleProperty ( 'width' ) ; this . adapter . setStyleProperty ( 'width' , width ) ; const { top , yTransformOrigin , left , xTransformOrigin } = this . hasCaret ? this . calculateTooltipWithCaretStyles ( this . anchorRect ) : this . calculateTooltipStyles ( this . anchorRect ) ; const transformProperty = HAS_WINDOW ? getCorrectPropertyName ( window , 'transform' ) : 'transform' ; this . adapter . setSurfaceAnimationStyleProperty ( `${ transformProperty }-origin` , `${ xTransformOrigin } ${ yTransformOrigin }` ) ; const leftAdjustment = left - ( this . parentRect ?. left ?? 0 ) ; const topAdjustment = top - ( this . parentRect ?. top ?? 0 ) ; this . adapter . setStyleProperty ( 'top' , `${ topAdjustment }px` ) ; this . adapter . setStyleProperty ( 'left' , `${ leftAdjustment }px` ) ; } /**
     * Calculates the position of the tooltip. A tooltip will be placed beneath
     * the anchor element and aligned either with the 'start'/'end' edge of the
     * anchor element or the 'center'.
     *
     * Tooltip alignment is selected such that the tooltip maintains a threshold
     * distance away from the viewport (defaulting to 'center' alignment). If the
     * placement of the anchor prevents this threshold distance from being
     * maintained, the tooltip is positioned so that it does not collide with the
     * viewport.
     *
     * Users can specify an alignment, however, if this alignment results in the
     * tooltip colliding with the viewport, this specification is overwritten.
     */ private calculateTooltipStyles ( anchorRect ) { if ( ! anchorRect ) { return { top : 0 , left : 0 } ; } const tooltipSize = this . adapter . getTooltipSize ( ) ; const top = this . calculateYTooltipDistance ( anchorRect , tooltipSize . height ) ; const left = this . calculateXTooltipDistance ( anchorRect , tooltipSize . width ) ; return { top : top . distance , yTransformOrigin : top . yTransformOrigin , left : left . distance , xTransformOrigin : left . xTransformOrigin } ; } /**
     * Calculates the `left` distance for the tooltip.
     * Returns the distance value and a string indicating the x-axis transform-
     * origin that should be used when animating the tooltip.
     */ private calculateXTooltipDistance ( anchorRect , tooltipWidth ) { const isLTR = ! this . adapter . isRTL ( ) ; let startPos , endPos , centerPos ; let startTransformOrigin , endTransformOrigin ; if ( this . richTooltip ) { startPos = isLTR ? anchorRect . left - tooltipWidth : anchorRect . right ; endPos = isLTR ? anchorRect . right : anchorRect . left - tooltipWidth ; startTransformOrigin = isLTR ? strings . RIGHT : strings . LEFT ; endTransformOrigin = isLTR ? strings . LEFT : strings . RIGHT ; } else { startPos = isLTR ? anchorRect . left : anchorRect . right - tooltipWidth ; endPos = isLTR ? anchorRect . right - tooltipWidth : anchorRect . left ; centerPos = anchorRect . left + ( anchorRect . width - tooltipWidth ) / 2 ; startTransformOrigin = isLTR ? strings . LEFT : strings . RIGHT ; endTransformOrigin = isLTR ? strings . RIGHT : strings . LEFT ; } const positionOptions = this . richTooltip ? this . determineValidPositionOptions ( startPos , endPos ) : this . determineValidPositionOptions ( centerPos ! , startPos , endPos ) ; if ( this . xTooltipPos === XPosition . START && positionOptions . has ( startPos ) ) { return { distance : startPos , xTransformOrigin : startTransformOrigin } ; } if ( this . xTooltipPos === XPosition . END && positionOptions . has ( endPos ) ) { return { distance : endPos , xTransformOrigin : endTransformOrigin } ; } if ( this . xTooltipPos === XPosition . CENTER && positionOptions . has ( centerPos ) ) { return { distance : centerPos ! , xTransformOrigin : strings . CENTER } ; } const possiblePositions = this . richTooltip ? [ { distance : endPos , xTransformOrigin : endTransformOrigin } , { distance : startPos , xTransformOrigin : startTransformOrigin } ] : [ { distance : centerPos ! , xTransformOrigin : strings . CENTER } , { distance : startPos , xTransformOrigin : startTransformOrigin } , { distance : endPos , xTransformOrigin : endTransformOrigin } ] ; const validPosition = possiblePositions . find ( ( { distance } ) => positionOptions . has ( distance ) ) ; if ( validPosition ) { return validPosition ; } if ( anchorRect . left < 0 ) { return { distance : this . minViewportTooltipThreshold , xTransformOrigin : strings . LEFT } ; } else { const viewportWidth = this . adapter . getViewportWidth ( ) ; const distance = viewportWidth - ( tooltipWidth + this . minViewportTooltipThreshold ) ; return { distance , xTransformOrigin : strings . RIGHT } ; } } /**
     * Given the values for the horizontal alignments of the tooltip, calculates
     * which of these options would result in the tooltip maintaining the required
     * threshold distance vs which would result in the tooltip staying within the
     * viewport.
     *
     * A Set of values is returned holding the distances that would honor the
     * above requirements. Following the logic for determining the tooltip
     * position, if all alignments violate the threshold, then the returned Set
     * contains values that keep the tooltip within the viewport.
     */ private determineValidPositionOptions ( ... positions ) { const posWithinThreshold = new Set ( ) ; const posWithinViewport = new Set ( ) ; for ( const position of positions ) { if ( this . positionHonorsViewportThreshold ( position ) ) { posWithinThreshold . add ( position ) ; } else if ( this . positionDoesntCollideWithViewport ( position ) ) { posWithinViewport . add ( position ) ; } } return posWithinThreshold . size ? posWithinThreshold : posWithinViewport ; } private positionHonorsViewportThreshold ( leftPos ) { const viewportWidth = this . adapter . getViewportWidth ( ) ; const tooltipWidth = this . adapter . getTooltipSize ( ) . width ; return leftPos + tooltipWidth <= viewportWidth - this . minViewportTooltipThreshold && leftPos >= this . minViewportTooltipThreshold ; } private positionDoesntCollideWithViewport ( leftPos ) { const viewportWidth = this . adapter . getViewportWidth ( ) ; const tooltipWidth = this . adapter . getTooltipSize ( ) . width ; return leftPos + tooltipWidth <= viewportWidth && leftPos >= 0 ; } /**
     * Calculates the `top` distance for the tooltip.
     * Returns the distance value and a string indicating the y-axis transform-
     * origin that should be used when animating the tooltip.
     */ private calculateYTooltipDistance ( anchorRect , tooltipHeight ) { const belowYPos = anchorRect . bottom + this . anchorGap ; const aboveYPos = anchorRect . top - ( this . anchorGap + tooltipHeight ) ; const yPositionOptions = this . determineValidYPositionOptions ( aboveYPos , belowYPos ) ; if ( this . yTooltipPos === YPosition . ABOVE && yPositionOptions . has ( aboveYPos ) ) { return { distance : aboveYPos , yTransformOrigin : strings . BOTTOM } ; } else if ( this . yTooltipPos === YPosition . BELOW && yPositionOptions . has ( belowYPos ) ) { return { distance : belowYPos , yTransformOrigin : strings . TOP } ; } if ( yPositionOptions . has ( belowYPos ) ) { return { distance : belowYPos , yTransformOrigin : strings . TOP } ; } if ( yPositionOptions . has ( aboveYPos ) ) { return { distance : aboveYPos , yTransformOrigin : strings . BOTTOM } ; } return { distance : belowYPos , yTransformOrigin : strings . TOP } ; } /**
     * Given the values for above/below alignment of the tooltip, calculates
     * which of these options would result in the tooltip maintaining the required
     * threshold distance vs which would result in the tooltip staying within the
     * viewport.
     *
     * A Set of values is returned holding the distances that would honor the
     * above requirements. Following the logic for determining the tooltip
     * position, if all possible alignments violate the threshold, then the
     * returned Set contains values that keep the tooltip within the viewport.
     */ private determineValidYPositionOptions ( aboveAnchorPos , belowAnchorPos ) { const posWithinThreshold = new Set ( ) ; const posWithinViewport = new Set ( ) ; if ( this . yPositionHonorsViewportThreshold ( aboveAnchorPos ) ) { posWithinThreshold . add ( aboveAnchorPos ) ; } else if ( this . yPositionDoesntCollideWithViewport ( aboveAnchorPos ) ) { posWithinViewport . add ( aboveAnchorPos ) ; } if ( this . yPositionHonorsViewportThreshold ( belowAnchorPos ) ) { posWithinThreshold . add ( belowAnchorPos ) ; } else if ( this . yPositionDoesntCollideWithViewport ( belowAnchorPos ) ) { posWithinViewport . add ( belowAnchorPos ) ; } return posWithinThreshold . size ? posWithinThreshold : posWithinViewport ; } private yPositionHonorsViewportThreshold ( yPos ) { const viewportHeight = this . adapter . getViewportHeight ( ) ; const tooltipHeight = this . adapter . getTooltipSize ( ) . height ; return yPos + tooltipHeight + this . minViewportTooltipThreshold <= viewportHeight && yPos >= this . minViewportTooltipThreshold ; } private yPositionDoesntCollideWithViewport ( yPos ) { const viewportHeight = this . adapter . getViewportHeight ( ) ; const tooltipHeight = this . adapter . getTooltipSize ( ) . height ; return yPos + tooltipHeight <= viewportHeight && yPos >= 0 ; } private calculateTooltipWithCaretStyles ( anchorRect ) { this . adapter . clearTooltipCaretStyles ( ) ; const caretSize = this . adapter . getTooltipCaretBoundingRect ( ) ; if ( ! anchorRect || ! caretSize ) { return { position : PositionWithCaret . DETECTED , top : 0 , left : 0 } ; } const caretWidth = caretSize . width / numbers . ANIMATION_SCALE ; const caretHeight = ( caretSize . height / numbers . ANIMATION_SCALE ) / 2 ; const tooltipSize = this . adapter . getTooltipSize ( ) ; const yOptions = this . calculateYWithCaretDistanceOptions ( anchorRect , tooltipSize . height , { caretWidth , caretHeight } ) ; const xOptions = this . calculateXWithCaretDistanceOptions ( anchorRect , tooltipSize . width , { caretWidth , caretHeight } ) ; let positionOptions = this . validateTooltipWithCaretDistances ( yOptions , xOptions ) ; if ( positionOptions . size < 1 ) { positionOptions = this . generateBackupPositionOption ( anchorRect , tooltipSize , { caretWidth , caretHeight } ) ; } const { position , xDistance , yDistance } = this . determineTooltipWithCaretDistance ( positionOptions ) ; const { yTransformOrigin , xTransformOrigin } = this . setCaretPositionStyles ( position , { caretWidth , caretHeight } ) ; return { yTransformOrigin , xTransformOrigin , top : yDistance , left : xDistance } ; } private calculateXWithCaretDistanceOptions ( anchorRect , tooltipWidth , caretSize ) { const { caretWidth , caretHeight } = caretSize ; const isLTR = ! this . adapter . isRTL ( ) ; const anchorMidpoint = anchorRect . left + anchorRect . width / 2 ; const sideLeftAligned = anchorRect . left - ( tooltipWidth + this . anchorGap + caretHeight ) ; const sideRightAligned = anchorRect . right + this . anchorGap + caretHeight ; const sideStartPos = isLTR ? sideLeftAligned : sideRightAligned ; const sideEndPos = isLTR ? sideRightAligned : sideLeftAligned ; const verticalLeftAligned = anchorMidpoint - ( numbers . CARET_INDENTATION + caretWidth / 2 ) ; const verticalRightAligned = anchorMidpoint - ( tooltipWidth - numbers . CARET_INDENTATION - caretWidth / 2 ) ; const verticalStartPos = isLTR ? verticalLeftAligned : verticalRightAligned ; const verticalEndPos = isLTR ? verticalRightAligned : verticalLeftAligned ; const verticalCenterPos = anchorMidpoint - tooltipWidth / 2 ; const possiblePositionsMap = new Map ( [ [ XPositionWithCaret . START , verticalStartPos ] , [ XPositionWithCaret . CENTER , verticalCenterPos ] , [ XPositionWithCaret . END , verticalEndPos ] , [ XPositionWithCaret . SIDE_END , sideEndPos ] , [ XPositionWithCaret . SIDE_START , sideStartPos ] , ] ) ; return possiblePositionsMap ; } private calculateYWithCaretDistanceOptions ( anchorRect , tooltipHeight , caretSize ) { const { caretWidth , caretHeight } = caretSize ; const anchorMidpoint = anchorRect . top + anchorRect . height / 2 ; const belowYPos = anchorRect . bottom + this . anchorGap + caretHeight ; const aboveYPos = anchorRect . top - ( this . anchorGap + tooltipHeight + caretHeight ) ; const sideTopYPos = anchorMidpoint - ( numbers . CARET_INDENTATION + caretWidth / 2 ) ; const sideCenterYPos = anchorMidpoint - ( tooltipHeight / 2 ) ; const sideBottomYPos = anchorMidpoint - ( tooltipHeight - numbers . CARET_INDENTATION - caretWidth / 2 ) ; const possiblePositionsMap = new Map ( [ [ YPositionWithCaret . ABOVE , aboveYPos ] , [ YPositionWithCaret . BELOW , belowYPos ] , [ YPositionWithCaret . SIDE_TOP , sideTopYPos ] , [ YPositionWithCaret . SIDE_CENTER , sideCenterYPos ] , [ YPositionWithCaret . SIDE_BOTTOM , sideBottomYPos ] , ] ) ; return possiblePositionsMap ; } private repositionTooltipOnAnchorMove ( ) { const newAnchorRect = this . adapter . getAnchorBoundingRect ( ) ; if ( ! newAnchorRect || ! this . anchorRect ) return ; if ( newAnchorRect . top !== this . anchorRect . top || newAnchorRect . left !== this . anchorRect . left || newAnchorRect . height !== this . anchorRect . height || newAnchorRect . width !== this . anchorRect . width ) { this . anchorRect = newAnchorRect ; this . parentRect = this . adapter . getParentBoundingRect ( ) ; this . richTooltip ? this . positionRichTooltip ( ) : this . positionPlainTooltip ( ) ; } } /**
     * Given a list of x/y position options for a rich tooltip with caret, checks
     * if valid x/y combinations of these position options are either within the
     * viewport threshold, or simply within the viewport. Returns a map with the
     * valid x/y position combinations that all either honor the viewport
     * threshold or are simply inside within the viewport.
     */ private validateTooltipWithCaretDistances ( yOptions , xOptions ) { const posWithinThreshold = new Map < PositionWithCaret , { xDistance : number ; yDistance : number ; } > ( ) ; const posWithinViewport = new Map < PositionWithCaret , { xDistance : number ; yDistance : number ; } > ( ) ; const validMappings = new Map ( [ [ YPositionWithCaret . ABOVE , [ XPositionWithCaret . START , XPositionWithCaret . CENTER , XPositionWithCaret . END ] ] , [ YPositionWithCaret . BELOW , [ XPositionWithCaret . START , XPositionWithCaret . CENTER , XPositionWithCaret . END ] ] , [ YPositionWithCaret . SIDE_TOP , [ XPositionWithCaret . SIDE_START , XPositionWithCaret . SIDE_END ] ] , [ YPositionWithCaret . SIDE_CENTER , [ XPositionWithCaret . SIDE_START , XPositionWithCaret . SIDE_END ] ] , [ YPositionWithCaret . SIDE_BOTTOM , [ XPositionWithCaret . SIDE_START , XPositionWithCaret . SIDE_END ] ] , ] ) ; for ( const y of validMappings . keys ( ) ) { const yDistance = yOptions . get ( y ) ! ; if ( this . yPositionHonorsViewportThreshold ( yDistance ) ) { for ( const x of validMappings . get ( y ) ! ) { const xDistance = xOptions . get ( x ) ! ; if ( this . positionHonorsViewportThreshold ( xDistance ) ) { const caretPositionName = this . caretPositionOptionsMapping ( x , y ) ; posWithinThreshold . set ( caretPositionName , { xDistance , yDistance } ) ; } } } else if ( this . yPositionDoesntCollideWithViewport ( yDistance ) ) { for ( const x of validMappings . get ( y ) ! ) { const xDistance = xOptions . get ( x ) ! ; if ( this . positionDoesntCollideWithViewport ( xDistance ) ) { const caretPositionName = this . caretPositionOptionsMapping ( x , y ) ; posWithinViewport . set ( caretPositionName , { xDistance , yDistance } ) ; } } } } return posWithinThreshold . size ? posWithinThreshold : posWithinViewport ; } /**
     * Method for generating a horizontal and vertical position for the tooltip if
     * all other calculated values are considered invalid. This would only happen
     * in situations of very small viewports/large tooltips.
     */ private generateBackupPositionOption ( anchorRect , tooltipSize , caretSize ) { const isLTR = ! this . adapter . isRTL ( ) ; let xDistance ; let xPos ; if ( anchorRect . left < 0 ) { xDistance = this . minViewportTooltipThreshold + caretSize . caretHeight ; xPos = isLTR ? XPositionWithCaret . END : XPositionWithCaret . START ; } else { const viewportWidth = this . adapter . getViewportWidth ( ) ; xDistance = viewportWidth - ( tooltipSize . width + this . minViewportTooltipThreshold + caretSize . caretHeight ) ; xPos = isLTR ? XPositionWithCaret . START : XPositionWithCaret . END ; } let yDistance ; let yPos ; if ( anchorRect . top < 0 ) { yDistance = this . minViewportTooltipThreshold + caretSize . caretHeight ; yPos = YPositionWithCaret . BELOW ; } else { const viewportHeight = this . adapter . getViewportHeight ( ) ; yDistance = viewportHeight - ( tooltipSize . height + this . minViewportTooltipThreshold + caretSize . caretHeight ) ; yPos = YPositionWithCaret . ABOVE ; } const caretPositionName = this . caretPositionOptionsMapping ( xPos , yPos ) ; return new Map < PositionWithCaret , { xDistance : number ; yDistance : number ; } > ( [ [ caretPositionName , { xDistance , yDistance } ] ] ) ; } /**
     * Given a list of valid position options for a rich tooltip with caret,
     * returns the option that should be used.
     */ private determineTooltipWithCaretDistance ( options ) { if ( options . has ( this . tooltipPositionWithCaret ) ) { const tooltipPos = options . get ( this . tooltipPositionWithCaret ) ! ; return { position : this . tooltipPositionWithCaret , xDistance : tooltipPos . xDistance , yDistance : tooltipPos . yDistance , } ; } const orderPref = [ PositionWithCaret . ABOVE_START , PositionWithCaret . ABOVE_CENTER , PositionWithCaret . ABOVE_END , PositionWithCaret . TOP_SIDE_START , PositionWithCaret . CENTER_SIDE_START , PositionWithCaret . BOTTOM_SIDE_START , PositionWithCaret . TOP_SIDE_END , PositionWithCaret . CENTER_SIDE_END , PositionWithCaret . BOTTOM_SIDE_END , PositionWithCaret . BELOW_START , PositionWithCaret . BELOW_CENTER , PositionWithCaret . BELOW_END ] ; const validPosition = orderPref . find ( ( pos ) => options . has ( pos ) ) ! ; const pos = options . get ( validPosition ) ! ; return { position : validPosition , xDistance : pos . xDistance , yDistance : pos . yDistance , } ; } /**
     * Returns the corresponding PositionWithCaret enum for the proivded
     * XPositionWithCaret and YPositionWithCaret enums. This mapping exists so our
     * public API accepts only PositionWithCaret enums (as all combinations of
     * XPositionWithCaret and YPositionWithCaret are not valid), but internally we
     * can calculate the X and Y positions of a rich tooltip with caret
     * separately.
     */ private caretPositionOptionsMapping ( xPos , yPos ) { switch ( yPos ) { case YPositionWithCaret . ABOVE : if ( xPos === XPositionWithCaret . START ) { return PositionWithCaret . ABOVE_START ; } else if ( xPos === XPositionWithCaret . CENTER ) { return PositionWithCaret . ABOVE_CENTER ; } else if ( xPos === XPositionWithCaret . END ) { return PositionWithCaret . ABOVE_END ; } break ; case YPositionWithCaret . BELOW : if ( xPos === XPositionWithCaret . START ) { return PositionWithCaret . BELOW_START ; } else if ( xPos === XPositionWithCaret . CENTER ) { return PositionWithCaret . BELOW_CENTER ; } else if ( xPos === XPositionWithCaret . END ) { return PositionWithCaret . BELOW_END ; } break ; case YPositionWithCaret . SIDE_TOP : if ( xPos === XPositionWithCaret . SIDE_START ) { return PositionWithCaret . TOP_SIDE_START ; } else if ( xPos === XPositionWithCaret . SIDE_END ) { return PositionWithCaret . TOP_SIDE_END ; } break ; case YPositionWithCaret . SIDE_CENTER : if ( xPos === XPositionWithCaret . SIDE_START ) { return PositionWithCaret . CENTER_SIDE_START ; } else if ( xPos === XPositionWithCaret . SIDE_END ) { return PositionWithCaret . CENTER_SIDE_END ; } break ; case YPositionWithCaret . SIDE_BOTTOM : if ( xPos === XPositionWithCaret . SIDE_START ) { return PositionWithCaret . BOTTOM_SIDE_START ; } else if ( xPos === XPositionWithCaret . SIDE_END ) { return PositionWithCaret . BOTTOM_SIDE_END ; } break ; default : break ; } throw new Error ( `MDCTooltipFoundation: Invalid caret position of ${ xPos }, ${ yPos }` ) ; } /**
     * Given a PositionWithCaret, applies the correct styles to the caret element
     * so that it is positioned properly on the rich tooltip.
     * Returns the x and y positions of the caret, to be used as the
     * transform-origin on the tooltip itself for entrance animations.
     */ private setCaretPositionStyles ( position , caretSize ) { const values = this . calculateCaretPositionOnTooltip ( position , caretSize ) ; if ( ! values ) { return { yTransformOrigin : 0 , xTransformOrigin : 0 } ; } this . adapter . clearTooltipCaretStyles ( ) ; this . adapter . setTooltipCaretStyle ( values . yAlignment , values . yAxisPx ) ; this . adapter . setTooltipCaretStyle ( values . xAlignment , values . xAxisPx ) ; const skewRadians = values . skew * ( Math . PI / 180 ) ; const scaleX = Math . cos ( skewRadians ) ; this . adapter . setTooltipCaretStyle ( 'transform' , `rotate(${ values . rotation }deg) skewY(${ values . skew }deg) scaleX(${ scaleX })` ) ; this . adapter . setTooltipCaretStyle ( 'transform-origin' , `${ values . xAlignment } ${ values . yAlignment }` ) ; for ( const corner of values . caretCorners ) { this . adapter . setTooltipCaretStyle ( corner , '0' ) ; } return { yTransformOrigin : values . yTransformOrigin , xTransformOrigin : values . xTransformOrigin } ; } /**
     * Given a PositionWithCaret, determines the correct styles to position the
     * caret properly on the rich tooltip.
     */ private calculateCaretPositionOnTooltip ( tooltipPos , caretSize ) { const isLTR = ! this . adapter . isRTL ( ) ; const tooltipWidth = this . adapter . getComputedStyleProperty ( 'width' ) ; const tooltipHeight = this . adapter . getComputedStyleProperty ( 'height' ) ; if ( ! tooltipWidth || ! tooltipHeight || ! caretSize ) { return ; } const midpointWidth = `calc((${ tooltipWidth } - ${ caretSize . caretWidth }px) / 2)` ; const midpointHeight = `calc((${ tooltipHeight } - ${ caretSize . caretWidth }px) / 2)` ; const flushWithEdge = '0' ; const indentedFromEdge = `${ numbers . CARET_INDENTATION }px` ; const indentedFromWidth = `calc(${ tooltipWidth } - ${ indentedFromEdge })` ; const indentedFromHeight = `calc(${ tooltipHeight } - ${ indentedFromEdge })` ; const verticalRotation = 35 ; const horizontalRotation = Math . abs ( 90 - verticalRotation ) ; const bottomRightTopLeftBorderRadius = [ 'border-bottom-right-radius' , 'border-top-left-radius' ] ; const bottomLeftTopRightBorderRadius = [ 'border-bottom-left-radius' , 'border-top-right-radius' ] ; const skewDeg = 20 ; switch ( tooltipPos ) { case PositionWithCaret . BELOW_CENTER : return { yAlignment : strings . TOP , xAlignment : strings . LEFT , yAxisPx : flushWithEdge , xAxisPx : midpointWidth , rotation : - 1 * verticalRotation , skew : - 1 * skewDeg , xTransformOrigin : midpointWidth , yTransformOrigin : flushWithEdge , caretCorners : bottomRightTopLeftBorderRadius , } ; case PositionWithCaret . BELOW_END : return { yAlignment : strings . TOP , xAlignment : isLTR ? strings . RIGHT : strings . LEFT , yAxisPx : flushWithEdge , xAxisPx : indentedFromEdge , rotation : isLTR ? verticalRotation : - 1 * verticalRotation , skew : isLTR ? skewDeg : - 1 * skewDeg , xTransformOrigin : isLTR ? indentedFromWidth : indentedFromEdge , yTransformOrigin : flushWithEdge , caretCorners : isLTR ? bottomLeftTopRightBorderRadius : bottomRightTopLeftBorderRadius , } ; case PositionWithCaret . BELOW_START : return { yAlignment : strings . TOP , xAlignment : isLTR ? strings . LEFT : strings . RIGHT , yAxisPx : flushWithEdge , xAxisPx : indentedFromEdge , rotation : isLTR ? - 1 * verticalRotation : verticalRotation , skew : isLTR ? - 1 * skewDeg : skewDeg , xTransformOrigin : isLTR ? indentedFromEdge : indentedFromWidth , yTransformOrigin : flushWithEdge , caretCorners : isLTR ? bottomRightTopLeftBorderRadius : bottomLeftTopRightBorderRadius , } ; case PositionWithCaret . TOP_SIDE_END : return { yAlignment : strings . TOP , xAlignment : isLTR ? strings . LEFT : strings . RIGHT , yAxisPx : indentedFromEdge , xAxisPx : flushWithEdge , rotation : isLTR ? horizontalRotation : - 1 * horizontalRotation , skew : isLTR ? - 1 * skewDeg : skewDeg , xTransformOrigin : isLTR ? flushWithEdge : tooltipWidth , yTransformOrigin : indentedFromEdge , caretCorners : isLTR ? bottomRightTopLeftBorderRadius : bottomLeftTopRightBorderRadius , } ; case PositionWithCaret . CENTER_SIDE_END : return { yAlignment : strings . TOP , xAlignment : isLTR ? strings . LEFT : strings . RIGHT , yAxisPx : midpointHeight , xAxisPx : flushWithEdge , rotation : isLTR ? horizontalRotation : - 1 * horizontalRotation , skew : isLTR ? - 1 * skewDeg : skewDeg , xTransformOrigin : isLTR ? flushWithEdge : tooltipWidth , yTransformOrigin : midpointHeight , caretCorners : isLTR ? bottomRightTopLeftBorderRadius : bottomLeftTopRightBorderRadius , } ; case PositionWithCaret . BOTTOM_SIDE_END : return { yAlignment : strings . BOTTOM , xAlignment : isLTR ? strings . LEFT : strings . RIGHT , yAxisPx : indentedFromEdge , xAxisPx : flushWithEdge , rotation : isLTR ? - 1 * horizontalRotation : horizontalRotation , skew : isLTR ? skewDeg : - 1 * skewDeg , xTransformOrigin : isLTR ? flushWithEdge : tooltipWidth , yTransformOrigin : indentedFromHeight , caretCorners : isLTR ? bottomLeftTopRightBorderRadius : bottomRightTopLeftBorderRadius , } ; case PositionWithCaret . TOP_SIDE_START : return { yAlignment : strings . TOP , xAlignment : isLTR ? strings . RIGHT : strings . LEFT , yAxisPx : indentedFromEdge , xAxisPx : flushWithEdge , rotation : isLTR ? - 1 * horizontalRotation : horizontalRotation , skew : isLTR ? skewDeg : - 1 * skewDeg , xTransformOrigin : isLTR ? tooltipWidth : flushWithEdge , yTransformOrigin : indentedFromEdge , caretCorners : isLTR ? bottomLeftTopRightBorderRadius : bottomRightTopLeftBorderRadius , } ; case PositionWithCaret . CENTER_SIDE_START : return { yAlignment : strings . TOP , xAlignment : isLTR ? strings . RIGHT : strings . LEFT , yAxisPx : midpointHeight , xAxisPx : flushWithEdge , rotation : isLTR ? - 1 * horizontalRotation : horizontalRotation , skew : isLTR ? skewDeg : - 1 * skewDeg , xTransformOrigin : isLTR ? tooltipWidth : flushWithEdge , yTransformOrigin : midpointHeight , caretCorners : isLTR ? bottomLeftTopRightBorderRadius : bottomRightTopLeftBorderRadius , } ; case PositionWithCaret . BOTTOM_SIDE_START : return { yAlignment : strings . BOTTOM , xAlignment : isLTR ? strings . RIGHT : strings . LEFT , yAxisPx : indentedFromEdge , xAxisPx : flushWithEdge , rotation : isLTR ? horizontalRotation : - 1 * horizontalRotation , skew : isLTR ? - 1 * skewDeg : skewDeg , xTransformOrigin : isLTR ? tooltipWidth : flushWithEdge , yTransformOrigin : indentedFromHeight , caretCorners : isLTR ? bottomRightTopLeftBorderRadius : bottomLeftTopRightBorderRadius , } ; case PositionWithCaret . ABOVE_CENTER : return { yAlignment : strings . BOTTOM , xAlignment : strings . LEFT , yAxisPx : flushWithEdge , xAxisPx : midpointWidth , rotation : verticalRotation , skew : skewDeg , xTransformOrigin : midpointWidth , yTransformOrigin : tooltipHeight , caretCorners : bottomLeftTopRightBorderRadius , } ; case PositionWithCaret . ABOVE_END : return { yAlignment : strings . BOTTOM , xAlignment : isLTR ? strings . RIGHT : strings . LEFT , yAxisPx : flushWithEdge , xAxisPx : indentedFromEdge , rotation : isLTR ? - 1 * verticalRotation : verticalRotation , skew : isLTR ? - 1 * skewDeg : skewDeg , xTransformOrigin : isLTR ? indentedFromWidth : indentedFromEdge , yTransformOrigin : tooltipHeight , caretCorners : isLTR ? bottomRightTopLeftBorderRadius : bottomLeftTopRightBorderRadius , } ; default : case PositionWithCaret . ABOVE_START : return { yAlignment : strings . BOTTOM , xAlignment : isLTR ? strings . LEFT : strings . RIGHT , yAxisPx : flushWithEdge , xAxisPx : indentedFromEdge , rotation : isLTR ? verticalRotation : - 1 * verticalRotation , skew : isLTR ? skewDeg : - 1 * skewDeg , xTransformOrigin : isLTR ? indentedFromEdge : indentedFromWidth , yTransformOrigin : tooltipHeight , caretCorners : isLTR ? bottomLeftTopRightBorderRadius : bottomRightTopLeftBorderRadius , } ; } } private clearShowTimeout ( ) { if ( this . showTimeout ) { clearTimeout ( this . showTimeout ) ; this . showTimeout = null ; } } private clearHideTimeout ( ) { if ( this . hideTimeout ) { clearTimeout ( this . hideTimeout ) ; this . hideTimeout = null ; } } /**
     * Method that allows user to specify additional elements that should have a
     * scroll event listener attached to it. This should be used in instances
     * where the anchor element is placed inside a scrollable container, and will
     * ensure that the tooltip will stay attached to the anchor on scroll.
     */ attachScrollHandler ( addEventListenerFn ) { this . addAncestorScrollEventListeners . push ( ( ) => { addEventListenerFn ( 'scroll' , this . windowScrollHandler ) ; } ) ; } /**
     * Must be used in conjunction with #attachScrollHandler. Removes the scroll
     * event handler from elements on the page.
     */ removeScrollHandler ( removeEventHandlerFn ) { this . removeAncestorScrollEventListeners . push ( ( ) => { removeEventHandlerFn ( 'scroll' , this . windowScrollHandler ) ; } ) ; } override destroy ( ) { if ( this . frameId ) { cancelAnimationFrame ( this . frameId ) ; this . frameId = null ; } this . clearHideTimeout ( ) ; this . clearShowTimeout ( ) ; this . adapter . removeClass ( SHOWN ) ; this . adapter . removeClass ( SHOWING_TRANSITION ) ; this . adapter . removeClass ( SHOWING ) ; this . adapter . removeClass ( HIDE ) ; this . adapter . removeClass ( HIDE_TRANSITION ) ; if ( this . richTooltip ) { this . adapter . deregisterEventHandler ( 'focusout' , this . richTooltipFocusOutHandler ) ; } if ( ! this . persistentTooltip ) { this . adapter . deregisterEventHandler ( 'mouseenter' , this . tooltipMouseEnterHandler ) ; this . adapter . deregisterEventHandler ( 'mouseleave' , this . tooltipMouseLeaveHandler ) ; } this . adapter . deregisterAnchorEventHandler ( 'blur' , this . anchorBlurHandler ) ; this . adapter . deregisterDocumentEventHandler ( 'click' , this . documentClickHandler ) ; this . adapter . deregisterDocumentEventHandler ( 'keydown' , this . documentKeydownHandler ) ; this . adapter . deregisterWindowEventHandler ( 'scroll' , this . windowScrollHandler ) ; this . adapter . deregisterWindowEventHandler ( 'resize' , this . windowResizeHandler ) ; for ( const fn of this . removeAncestorScrollEventListeners ) { fn ( ) ; } this . animFrame . cancelAll ( ) ; } } interface CaretPosOnTooltip { yAlignment : string ; xAlignment : string ; yAxisPx : string ; xAxisPx : string ; rotation : number ; skew : number ; xTransformOrigin : string ; yTransformOrigin : string ; caretCorners : Array < string > ; } export default MDCTooltipFoundation ;