import { MdcComponent } from '@aurelia-mdc-web/base' ; import { MDCTooltipFoundation , MDCTooltipAdapter , events , XPosition , YPosition , AnchorBoundaryType , attributes , CssClasses , PositionWithCaret , numbers } from '@material/tooltip' ; import { inject , customElement , useView , PLATFORM } from 'aurelia-framework' ; import { bindable } from 'aurelia-typed-observable-plugin' ; import { MdcDefaultTooltipConfiguration } from './mdc-default-tooltip-configuration' ; selector @ inject ( Element , MdcDefaultTooltipConfiguration ) @ useView ( PLATFORM . moduleName ( './mdc-tooltip.html' ) ) @ customElement ( 'mdc-tooltip' ) export class MdcTooltip extends MdcComponent < MDCTooltipFoundation > implements EventListenerObject { constructor ( root , private defaultConfiguration ) { super ( root ) ; } /** Sets the anchor element */ @ bindable anchorElem ? : HTMLElement ; @ bindable . booleanAttr rich : boolean ; @ bindable . booleanAttr persistent : boolean ; /** Sets the horizontal alignment of the tooltip */ @ bindable xPosition ? : keyof typeof XPosition ; async xPositionChanged ( ) { if ( this . xPosition !== undefined ) { await this . initialised ; this . foundation ?. setTooltipPosition ( { xPos : XPosition [ this . xPosition ] } ) ; } } /** Sets the vertical alignment of the tooltip */ @ bindable yPosition ? : keyof typeof YPosition ; async yPositionChanged ( ) { if ( this . yPosition !== undefined ) { await this . initialised ; this . foundation ?. setTooltipPosition ( { yPos : YPosition [ this . yPosition ] } ) ; } } /** Sets the caret position relative to the tooltip */ @ bindable withCaretPos ? : keyof typeof PositionWithCaret ; async withCaretPosChanged ( ) { if ( this . withCaretPos !== undefined ) { await this . initialised ; this . foundation ?. setTooltipPosition ( { withCaretPos : PositionWithCaret [ this . withCaretPos ] } ) ; } } /** Specifies whether the anchor element is bounded (element has an identifiable boundary such as a button) or unbounded (element does not have a visually declared boundary such as a text link).
     * Tooltips are placed closer to bounded anchor elements compared to unbounded anchor elements. If no type is specified, defaults to BOUNDED.
     **/ @ bindable boundaryType ? : keyof typeof AnchorBoundaryType ; async boundaryTypeChanged ( ) { if ( this . boundaryType !== undefined ) { await this . initialised ; this . foundation ?. setAnchorBoundaryType ( AnchorBoundaryType [ this . boundaryType ] ) ; } } @ bindable . number showDelay ? : number = this . defaultConfiguration . showDelay ; async showDelayChanged ( ) { await this . initialised ; this . foundation ?. setShowDelay ( this . showDelay ?? numbers . SHOW_DELAY_MS ) ; } @ bindable . number hideDelay ? : number = this . defaultConfiguration . hideDelay ; async hideDelayChanged ( ) { await this . initialised ; this . foundation ?. setHideDelay ( this . hideDelay ?? numbers . HIDE_DELAY_MS ) ; } @ bindable scrollHost ? : HTMLElement | string = this . defaultConfiguration . scrollHost ; async initialise ( ) { if ( this . persistent ) { this . root . setAttribute ( attributes . PERSISTENT , 'true' ) ; } } initialSyncWithDOM ( ) { if ( this . rich && this . persistent ) { this . anchorElem ?. addEventListener ( 'click' , this ) ; } else { this . anchorElem ?. addEventListener ( 'mouseenter' , this ) ; this . anchorElem ?. addEventListener ( 'focus' , this ) ; this . anchorElem ?. addEventListener ( 'mouseleave' , this ) ; this . anchorElem ?. addEventListener ( 'touchstart' , this ) ; this . anchorElem ?. addEventListener ( 'touchend' , this ) ; } if ( typeof ( this . scrollHost ) === 'string' ) { this . scrollHost = document . querySelector < HTMLElement > ( this . scrollHost ) ?? undefined ; } if ( this . scrollHost ) { const scrollHost = this . scrollHost ; this . foundation ! . attachScrollHandler ( ( event , listener ) => scrollHost . addEventListener ( event , listener ) ) ; this . foundation ! . removeScrollHandler ( ( event , listener ) => scrollHost . removeEventListener ( event , listener ) ) ; } } destroy ( ) { if ( ! this . anchorElem ) { return ; } if ( this . rich && this . persistent ) { this . anchorElem . removeEventListener ( 'click' , this ) ; } else { this . anchorElem . removeEventListener ( 'mouseenter' , this ) ; this . anchorElem . removeEventListener ( 'focus' , this ) ; this . anchorElem . removeEventListener ( 'mouseleave' , this ) ; this . anchorElem . removeEventListener ( 'touchstart' , this ) ; this . anchorElem . removeEventListener ( 'touchend' , this ) ; } } handleEvent ( evt ) { switch ( evt . type ) { case 'click' : this . handleClick ( ) ; break ; case 'mouseenter' : this . handleMouseEnter ( ) ; break ; case 'focus' : this . handleFocus ( evt as FocusEvent ) ; break ; case 'mouseleave' : this . handleMouseLeave ( ) ; break ; case 'touchstart' : this . handleTouchstart ( ) ; break ; case 'touchend' : this . handleTouchend ( ) ; break ; } } handleMouseEnter ( ) { this . foundation ?. handleAnchorMouseEnter ( ) ; } handleFocus ( evt ) { this . foundation ?. handleAnchorFocus ( evt ) ; } handleMouseLeave ( ) { this . foundation ?. handleAnchorMouseLeave ( ) ; } handleTouchstart ( ) { this . foundation ?. handleAnchorTouchstart ( ) ; } handleTouchend ( ) { this . foundation ?. handleAnchorTouchend ( ) ; } handleClick ( ) { this . foundation ?. handleAnchorClick ( ) ; } handleTransitionEnd ( ) { this . foundation ?. handleTransitionEnd ( ) ; } getDefaultFoundation ( ) { const adapter = { getAttribute : ( attr ) => this . root . getAttribute ( attr ) , setAttribute : ( attr , value ) => { this . root . setAttribute ( attr , value ) ; } , removeAttribute : ( attr ) => { this . root . removeAttribute ( attr ) ; } , addClass : ( className ) => { this . root . classList . add ( className ) ; } , hasClass : ( className ) => this . root . classList . contains ( className ) , removeClass : ( className ) => { this . root . classList . remove ( className ) ; } , getComputedStyleProperty : ( propertyName ) => { return window . getComputedStyle ( this . root ) . getPropertyValue ( propertyName ) ; } , setStyleProperty : ( propertyName , value ) => { this . root . style . setProperty ( propertyName , value ) ; } , setSurfaceAnimationStyleProperty : ( propertyName , value ) => { const surface = this . root . querySelector < HTMLElement > ( `.${ CssClasses . SURFACE_ANIMATION }` ) ; surface ?. style . setProperty ( propertyName , value ) ; } , getViewportWidth : ( ) => window . innerWidth , getViewportHeight : ( ) => window . innerHeight , getTooltipSize : ( ) => { return { width : this . root . offsetWidth , height : this . root . offsetHeight } ; } , getAnchorBoundingRect : ( ) => { return this . anchorElem ? this . anchorElem . getBoundingClientRect ( ) : null ; } , getParentBoundingRect : ( ) => { return this . root . parentElement ?. getBoundingClientRect ( ) ?? null ; } , getAnchorAttribute : ( attr ) => { return this . anchorElem ? this . anchorElem . getAttribute ( attr ) : null ; } , setAnchorAttribute : ( attr , value ) => { this . anchorElem ?. setAttribute ( attr , value ) ; } , isRTL : ( ) => getComputedStyle ( this . root ) . direction === 'rtl' , anchorContainsElement : ( element ) => { return ! ! this . anchorElem ?. contains ( element ) ; } , tooltipContainsElement : ( element ) => { return this . root . contains ( element ) ; } , focusAnchorElement : ( ) => { this . anchorElem ?. focus ( ) ; } , registerEventHandler : ( evt , handler ) => { if ( this . root instanceof HTMLElement ) { this . root . addEventListener ( evt , handler ) ; } } , deregisterEventHandler : ( evt , handler ) => { if ( this . root instanceof HTMLElement ) { this . root . removeEventListener ( evt , handler ) ; } } , registerAnchorEventHandler : ( evt , handler ) => { this . anchorElem ?. addEventListener ( evt , handler ) ; } , deregisterAnchorEventHandler : ( evt , handler ) => { this . anchorElem ?. addEventListener ( evt , handler ) ; } , registerDocumentEventHandler : ( evt , handler ) => { document . body . addEventListener ( evt , handler ) ; } , deregisterDocumentEventHandler : ( evt , handler ) => { document . body . removeEventListener ( evt , handler ) ; } , registerWindowEventHandler : ( evt , handler ) => { window . addEventListener ( evt , handler ) ; } , deregisterWindowEventHandler : ( evt , handler ) => { window . removeEventListener ( evt , handler ) ; } , notifyHidden : ( ) => { this . emit ( events . HIDDEN , { } ) ; } , getTooltipCaretBoundingRect : ( ) => { const caret = this . root . querySelector < HTMLElement > ( `.${ CssClasses . TOOLTIP_CARET_TOP }` ) ; if ( ! caret ) { return null ; } return caret . getBoundingClientRect ( ) ; } , setTooltipCaretStyle : ( propertyName , value ) => { const topCaret = this . root . querySelector < HTMLElement > ( `.${ CssClasses . TOOLTIP_CARET_TOP }` ) ; const bottomCaret = this . root . querySelector < HTMLElement > ( `.${ CssClasses . TOOLTIP_CARET_BOTTOM }` ) ; if ( ! topCaret || ! bottomCaret ) { return ; } topCaret . style . setProperty ( propertyName , value ) ; bottomCaret . style . setProperty ( propertyName , value ) ; } , clearTooltipCaretStyles : ( ) => { const topCaret = this . root . querySelector < HTMLElement > ( `.${ CssClasses . TOOLTIP_CARET_TOP }` ) ; const bottomCaret = this . root . querySelector < HTMLElement > ( `.${ CssClasses . TOOLTIP_CARET_BOTTOM }` ) ; if ( ! topCaret || ! bottomCaret ) { return ; } topCaret . removeAttribute ( 'style' ) ; bottomCaret . removeAttribute ( 'style' ) ; } , getActiveElement : ( ) => { return document . activeElement ; } , } ; return new MDCTooltipFoundation ( adapter ) ; } }