/**
 * -------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All Rights Reserved.  Licensed under the MIT License.
 * See License in the project root for license information.
 * -------------------------------------------------------------------------------------------
 */ import { Contact , Presence } from '@microsoft/microsoft-graph-types' ; import { customElement , html , internalProperty , property , TemplateResult } from 'lit-element' ; import { classMap } from 'lit-html/directives/class-map' ; import { findPeople , getEmailFromGraphEntity } from '../../graph/graph.people' ; import { getGroupImage , getPersonImage } from '../../graph/graph.photos' ; import { getUserPresence } from '../../graph/graph.presence' ; import { getUserWithPhoto } from '../../graph/graph.userWithPhoto' ; import { findUsers , getMe , getUser } from '../../graph/graph.user' ; import { AvatarSize , IDynamicPerson , ViewType } from '../../graph/types' ; import { Providers , ProviderState , MgtTemplatedComponent } from '@microsoft/mgt-element' ; import '../../styles/style-helper' ; import { getSvg , SvgIcon } from '../../utils/SvgHelper' ; import { MgtPersonCard } from '../mgt-person-card/mgt-person-card' ; import '../sub-components/mgt-flyout/mgt-flyout' ; import { MgtFlyout } from '../sub-components/mgt-flyout/mgt-flyout' ; import { PersonCardInteraction } from './../PersonCardInteraction' ; import { styles } from './mgt-person-css' ; export { PersonCardInteraction } from '../PersonCardInteraction' ; export enum { number } export enum PersonViewType { /**
     * Render only the avatar
     */ avatar = 2 , /**
     * Render the avatar and one line of text
     */ oneline = 3 , /**
     * Render the avatar and two lines of text
     */ twolines = 4 , /**
     * Render the avatar and three lines of text
     */ threelines = 5 } export enum avatarType { /**
     * Renders avatar photo if available, falls back to initials
     */ photo = 'photo' , /**
     * Forces render avatar initials
     */ initials = 'initials' } export interface export interface MgtPersonConfig { type { boolean } useContactApis : boolean ; } /**
 * Person properties part of original set provided by graph by default
 */ const defaultPersonProperties = [ 'businessPhones' , 'displayName' , 'givenName' , 'jobTitle' , 'mail' , 'mobilePhone' , 'officeLocation' , 'preferredLanguage' , 'surname' , 'userPrincipalName' , 'id' ] ; export class extends MgtTemplatedComponent fires fires fires cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop @ customElement ( 'mgt-person' ) export class MgtPerson extends MgtTemplatedComponent { /**
     * Array of styles to apply to the element. The styles should be defined
     * using the `css` tag function.
     */ static get styles ( ) { return styles ; } static type { MgtPersonConfig } memberof public static config : MgtPersonConfig = { useContactApis : true } ; type { string } @ property ( { attribute : 'person-query' } ) public get personQuery ( ) : string { return this . _personQuery ; } public set personQuery ( value ) { if ( value === this . _personQuery ) { return ; } this . _personQuery = value ; this . personDetailsInternal = null ; this . requestStateUpdate ( ) ; } type { IDynamicPerson } @ property ( { attribute : 'fallback-details' , type : Object } ) public get fallbackDetails ( ) : IDynamicPerson { return this . _fallbackDetails ; } public set fallbackDetails ( value ) { if ( value === this . _fallbackDetails ) { return ; } this . _fallbackDetails = value ; if ( this . personDetailsInternal ) { return ; } if ( value && value . displayName ) { this . _personAvatarBg = this . getColorFromName ( value . displayName ) ; } else { this . _personAvatarBg = 'gray20' ; } this . requestStateUpdate ( ) ; } type { string } @ property ( { attribute : 'user-id' } ) public get userId ( ) : string { return this . _userId ; } public set userId ( value ) { if ( value === this . _userId ) { return ; } this . _userId = value ; this . personDetailsInternal = null ; this . requestStateUpdate ( ) ; } type { boolean } @ property ( { attribute : 'show-presence' , type : Boolean } ) public showPresence : boolean ; type { AvatarSize } @ property ( { attribute : 'avatar-size' , type : String } ) public avatarSize : AvatarSize ; type { IDynamicPerson } @ property ( { attribute : null , type : Object } ) private get personDetailsInternal ( ) : IDynamicPerson { return this . _personDetailsInternal ; } private set personDetailsInternal ( value ) { if ( this . _personDetailsInternal === value ) { return ; } this . _personDetailsInternal = value ; if ( value && value . displayName ) { this . _personAvatarBg = this . getColorFromName ( value . displayName ) ; } else { this . _personAvatarBg = 'gray20' ; } this . _fetchedImage = null ; this . _fetchedPresence = null ; this . requestStateUpdate ( ) ; this . requestUpdate ( 'personDetailsInternal' ) ; } type { IDynamicPerson } @ property ( { attribute : 'person-details' , type : Object } ) public get personDetails ( ) : IDynamicPerson { return this . _personDetails ; } public set personDetails ( value ) { if ( this . _personDetails === value ) { return ; } this . _personDetails = value ; if ( value && value . displayName ) { this . _personAvatarBg = this . getColorFromName ( value . displayName ) ; } else { this . _personAvatarBg = 'gray20' ; } this . _fetchedImage = null ; this . _fetchedPresence = null ; this . requestStateUpdate ( ) ; this . requestUpdate ( 'personDetails' ) ; } type { string } memberof @ property ( { attribute : 'person-image' , type : String } ) public get personImage ( ) : string { return this . _personImage || this . _fetchedImage ; } public set personImage ( value ) { if ( value === this . _personImage ) { return ; } this . _isInvalidImageSrc = ! value ; const oldValue = this . _personImage ; this . _personImage = value ; this . requestUpdate ( 'personImage' , oldValue ) ; } type { boolean } memberof @ property ( { attribute : 'fetch-image' , type : Boolean } ) public fetchImage : boolean ; type { string } memberof @ property ( { attribute : 'avatar-type' , converter : value => { value = value . toLowerCase ( ) ; if ( value === 'initials' ) { return avatarType . initials ; } else { return avatarType . photo ; } } } ) public get avatarType ( ) : string { return this . _avatarType ; } public set avatarType ( value ) { if ( value === this . _avatarType ) { return ; } this . _avatarType = value ; this . requestStateUpdate ( ) ; } type { MicrosoftGraph . Presence } memberof @ property ( { attribute : 'person-presence' , type : Object } ) public get personPresence ( ) : Presence { return this . _personPresence || this . _fetchedPresence ; } public set personPresence ( value ) { if ( value === this . _personPresence ) { return ; } const oldValue = this . _personPresence ; this . _personPresence = value ; this . requestUpdate ( 'personPresence' , oldValue ) ; } type { PersonCardInteraction } memberof @ property ( { attribute : 'person-card' , converter : ( value , type ) => { value = value . toLowerCase ( ) ; if ( typeof PersonCardInteraction [ value ] === 'undefined' ) { return PersonCardInteraction . none ; } else { return PersonCardInteraction [ value ] ; } } } ) public personCardInteraction : PersonCardInteraction ; static return { * } memberof public static get requiredScopes ( ) : string [ ] { const scopes = [ 'user.readbasic.all' , 'user.read' , 'people.read' , 'presence.read.all' , 'presence.read' ] ; if ( MgtPerson . config . useContactApis ) { scopes . push ( 'contacts.read' ) ; } return scopes ; } protected type { MgtFlyout } memberof protected get flyout ( ) : MgtFlyout { return this . renderRoot . querySelector ( '.flyout' ) ; } type { string } memberof @ property ( { attribute : 'line1-property' } ) public line1Property : string ; type { string } memberof @ property ( { attribute : 'line2-property' } ) public line2Property : string ; type { string } memberof @ property ( { attribute : 'line3-property' } ) public line3Property : string ; type { ViewType | PersonViewType } memberof @ property ( { converter : value => { if ( ! value || value . length === 0 ) { return ViewType . image ; } value = value . toLowerCase ( ) ; if ( typeof ViewType [ value ] === 'undefined' ) { return ViewType . image ; } else { return ViewType [ value ] ; } } } ) public view : ViewType | PersonViewType ; @ internalProperty ( ) private _fetchedImage : string ; @ internalProperty ( ) private _fetchedPresence : Presence ; @ internalProperty ( ) private _isInvalidImageSrc : boolean ; @ internalProperty ( ) private _personCardShouldRender : boolean ; private _personDetailsInternal : IDynamicPerson ; private _personDetails : IDynamicPerson ; private _fallbackDetails : IDynamicPerson ; private _personAvatarBg : string ; private _personImage : string ; private _personPresence : Presence ; private _personQuery : string ; private _userId : string ; private _avatarType : string ; private _mouseLeaveTimeout ; private _mouseEnterTimeout ; constructor ( ) { super ( ) ; this . personCardInteraction = PersonCardInteraction . none ; this . line1Property = 'displayName' ; this . line2Property = 'email' ; this . line3Property = 'jobTitle' ; this . view = ViewType . image ; this . avatarSize = 'auto' ; this . _isInvalidImageSrc = false ; this . _avatarType = 'photo' ; } /**
     * Invoked on each update to perform rendering tasks. This method must return
     * a lit-html TemplateResult. Setting properties inside this method will *not*
     * trigger the element to update.
     */ public render ( ) { if ( this . isLoadingState && ! this . personDetails && ! this . personDetailsInternal && ! this . fallbackDetails ) { return this . renderLoading ( ) ; } const person = this . personDetails || this . personDetailsInternal || this . fallbackDetails ; const image = this . getImage ( ) ; const presence = this . personPresence || this . _fetchedPresence ; if ( ! person && ! image ) { return this . renderNoData ( ) ; } let personTemplate = this . renderTemplate ( 'default' , { person , personImage : image , personPresence : presence } ) ; if ( ! personTemplate ) { const detailsTemplate = this . renderDetails ( person , presence ) ; const imageWithPresenceTemplate = this . renderAvatar ( person , image , presence ) ; const rootClasses = { 'person-root' : true , clickable : this . personCardInteraction === PersonCardInteraction . click } ; personTemplate = html `
        <div class=${ classMap ( rootClasses ) }>
          ${ imageWithPresenceTemplate } ${ detailsTemplate }
        </div>
      ` ; } if ( this . personCardInteraction !== PersonCardInteraction . none ) { personTemplate = this . renderFlyout ( personTemplate , person , image , presence ) ; } return html `
      <div
        class="root"
        dir=${ this . direction }
        @click=${ this . handleMouseClick }
        @mouseenter=${ this . handleMouseEnter }
        @mouseleave=${ this . handleMouseLeave }
        @keydown=${ this . handleKeyDown }
        tabindex=0
      >
        ${ personTemplate }
      </div>
    ` ; } protected returns { TemplateResult } memberof protected renderLoading ( ) { return this . renderTemplate ( 'loading' , null ) || html `` ; } protected memberof protected clearState ( ) { this . _personImage = '' ; this . _personDetailsInternal = null ; this . _fetchedImage = null ; this . _fetchedPresence = null ; } protected returns { TemplateResult } memberof protected renderNoData ( ) { const noDataTemplate = this . renderTemplate ( 'no-data' , null ) ; if ( noDataTemplate ) { return noDataTemplate ; } const avatarClasses = { 'avatar-icon' : true , 'ms-Icon' : true , 'ms-Icon--Contact' : true , small : ! this . isLargeAvatar ( ) } ; return html `
      <i class=${ classMap ( avatarClasses ) }></i>
    ` ; } protected param { string } imageSrc param { IDynamicPerson } personDetailsInternal returns memberof protected renderImage ( personDetailsInternal , imageSrc ) { const title = personDetailsInternal && this . personCardInteraction === PersonCardInteraction . none ? personDetailsInternal . displayName || getEmailFromGraphEntity ( personDetailsInternal ) || '' : '' ; if ( imageSrc && ! this . _isInvalidImageSrc && this . _avatarType === 'photo' ) { return html `
        <div class="img-wrapper">
          <img alt=${ personDetailsInternal . displayName } src=${ imageSrc } @error=${ ( ) => ( this . _isInvalidImageSrc = true ) } />
        </div>
      ` ; } else if ( personDetailsInternal ) { const initials = this . getInitials ( personDetailsInternal ) ; return html `
        <span class="initials-text" aria-label="${ initials }">
          ${ initials && initials . length ? html `
                ${ initials }
              ` : html `
                <i class="ms-Icon ms-Icon--Contact contact-icon"></i>
              ` }
        </span>
      ` ; } } protected param memberof protected renderPresence ( presence ) { if ( ! this . showPresence || ! presence ) { return html `` ; } let statusClass = null ; switch ( presence . availability ) { case 'DoNotDisturb' : switch ( presence . activity ) { case 'OutOfOffice' : statusClass = 'presence-oof-dnd' ; break ; default : statusClass = 'presence-dnd' ; break ; } break ; case 'BeRightBack' : statusClass = 'presence-away' ; break ; case 'Available' : switch ( presence . activity ) { case 'Available' : statusClass = 'presence-available' ; break ; case 'OutOfOffice' : statusClass = 'presence-oof-available' ; break ; } break ; case 'Busy' : switch ( presence . activity ) { case 'OutOfOffice' : statusClass = 'presence-oof-busy' ; break ; default : statusClass = 'presence-busy' ; break ; } break ; case 'Away' : switch ( presence . activity ) { case 'Away' : statusClass = 'presence-away' ; break ; case 'OutOfOffice' : statusClass = 'presence-oof-offline' ; break ; } break ; case 'Offline' : switch ( presence . activity ) { case 'Offline' : statusClass = 'presence-offline' ; break ; case 'OutOfOffice' : statusClass = 'presence-oof-offline' ; break ; case 'OffWork' : statusClass = 'presence-offline' ; break ; } break ; default : statusClass = 'presence-offline' ; break ; } const presenceClasses = { 'ms-Icon' : true , 'presence-basic' : true } ; presenceClasses [ statusClass ] = true ; let iconHtml = null ; if ( statusClass === 'presence-oof-offline' ) { iconHtml = html `
        <div class="ms-Icon presence-basic presence-oof-offline-wrapper">
          <i class="presence-oof-offline">
            ${ getSvg ( SvgIcon . SkypeArrow , '#666666' ) }
          </i>
        </div>
      ` ; } else { iconHtml = html `
        <i class=${ classMap ( presenceClasses ) } aria-hidden="true"></i>
      ` ; } return html `
      <div class="user-presence" title=${ presence . activity } aria-label=${ presence . activity } role="img">
        ${ iconHtml }
      </div>
    ` ; } protected param memberof protected renderAvatar ( personDetailsInternal , image , presence ) { const title = personDetailsInternal && this . personCardInteraction === PersonCardInteraction . none ? personDetailsInternal . displayName || getEmailFromGraphEntity ( personDetailsInternal ) || '' : '' ; const imageClasses = { initials : ! image || this . _isInvalidImageSrc || this . _avatarType === 'initials' , small : ! this . isLargeAvatar ( ) , 'user-avatar' : true } ; if ( ( ! image || this . _isInvalidImageSrc || this . _avatarType === 'initials' ) && personDetailsInternal ) { imageClasses [ this . _personAvatarBg ] = true ; } const imageTemplate = this . renderImage ( personDetailsInternal , image ) ; const presenceTemplate = this . renderPresence ( presence ) ; return html `
      <div class=${ classMap ( imageClasses ) } title=${ title } aria-label=${ title }>
        ${ imageTemplate } ${ presenceTemplate }
      </div>
    ` ; } private handleLine1Clicked ( ) { this . fireCustomEvent ( 'line1clicked' , this . personDetailsInternal ) ; } private handleLine2Clicked ( ) { this . fireCustomEvent ( 'line2clicked' , this . personDetailsInternal ) ; } private handleLine3Clicked ( ) { this . fireCustomEvent ( 'line3clicked' , this . personDetailsInternal ) ; } protected param { IDynamicPerson } person param { string } image returns { TemplateResult } memberof protected renderDetails ( personProps , presence ? ) { if ( ! personProps || this . view === ViewType . image || this . view === PersonViewType . avatar ) { return html `` ; } let person = personProps ; if ( presence ) { person . presenceActivity = presence ?. activity ; person . presenceAvailability = presence ?. availability ; } const details = [ ] ; if ( this . view > ViewType . image ) { if ( this . hasTemplate ( 'line1' ) ) { const template = this . renderTemplate ( 'line1' , { person } ) ; details . push ( html `
          <div class="line1" @click=${ ( ) => this . handleLine1Clicked ( ) }>${ template }</div>
        ` ) ; } else { const text = this . getTextFromProperty ( person , this . line1Property ) ; if ( text ) { details . push ( html `
            <div class="line1" @click=${ ( ) => this . handleLine1Clicked ( ) } aria-label="${ text }">${ text }</div>
          ` ) ; } } } if ( this . view > ViewType . oneline ) { if ( this . hasTemplate ( 'line2' ) ) { const template = this . renderTemplate ( 'line2' , { person } ) ; details . push ( html `
          <div class="line2" @click=${ ( ) => this . handleLine2Clicked ( ) }>${ template }</div>
        ` ) ; } else { const text = this . getTextFromProperty ( person , this . line2Property ) ; if ( text ) { details . push ( html `
            <div class="line2" @click=${ ( ) => this . handleLine2Clicked ( ) } aria-label="${ text }">${ text }</div>
          ` ) ; } } } if ( this . view > ViewType . twolines ) { if ( this . hasTemplate ( 'line3' ) ) { const template = this . renderTemplate ( 'line3' , { person } ) ; details . push ( html `
          <div class="line3" @click=${ ( ) => this . handleLine3Clicked ( ) }>${ template }</div>
        ` ) ; } else { const text = this . getTextFromProperty ( person , this . line3Property ) ; if ( text ) { details . push ( html `
            <div class="line3" @click=${ ( ) => this . handleLine3Clicked ( ) } aria-label="${ text }">${ text }</div>
          ` ) ; } } } const detailsClasses = classMap ( { details : true , small : ! this . isLargeAvatar ( ) } ) ; return html `
      <div class="${ detailsClasses }">
        ${ details }
      </div>
    ` ; } protected returns { TemplateResult } memberof protected renderFlyout ( anchor , personDetails , image , presence ) { const flyoutContent = this . _personCardShouldRender ? html `
          <div slot="flyout">
            ${ this . renderFlyoutContent ( personDetails , image , presence ) }
          </div>
        ` : html `` ; return html `
      <mgt-flyout light-dismiss class="flyout" .avoidHidingAnchor=${ false }>
        ${ anchor } ${ flyoutContent }
      </mgt-flyout>
    ` ; } protected returns { TemplateResult } memberof protected renderFlyoutContent ( personDetails , image , presence ) { return ( this . renderTemplate ( 'person-card' , { person : personDetails , personImage : image } ) || html `
        <mgt-person-card
          .personDetails=${ personDetails }
          .personImage=${ image }
          .personPresence=${ presence }
          .showPresence=${ this . showPresence }
        ></mgt-person-card>
      ` ) ; } protected returns memberof protected async loadState ( ) { const provider = Providers . globalProvider ; if ( ! provider || provider . state === ProviderState . Loading ) { return ; } if ( provider && provider . state === ProviderState . SignedOut ) { this . personDetailsInternal = null ; return ; } const graph = provider . graph . forComponent ( this ) ; let personProps = [ ... defaultPersonProperties , this . line1Property , this . line2Property , this . line3Property ] ; personProps = personProps . filter ( email => email !== 'email' ) ; let details = this . personDetailsInternal || this . personDetails ; if ( details ) { if ( ! details . personImage && this . fetchImage && this . _avatarType === 'photo' && ! this . personImage && ! this . _fetchedImage ) { details ; let image ; if ( 'groupTypes' in details ) { image = await getGroupImage ( graph , details , MgtPerson . config . useContactApis ) ; } else { image = await getPersonImage ( graph , details , MgtPerson . config . useContactApis ) ; } if ( image ) { details . personImage = image ; this . _fetchedImage = image ; } } } else if ( this . userId || this . personQuery === 'me' ) { let person ; if ( this . _avatarType === 'photo' ) { person = await getUserWithPhoto ( graph , this . userId , personProps ) ; } else { if ( this . personQuery === 'me' ) { person = await getMe ( graph , personProps ) ; } else { person = await getUser ( graph , this . userId , personProps ) ; } } this . personDetailsInternal = person ; this . _fetchedImage = this . getImage ( ) ; } else if ( this . personQuery ) { let people = await findPeople ( graph , this . personQuery , 1 ) ; if ( ! people || people . length === 0 ) { people = ( await findUsers ( graph , this . personQuery , 1 ) ) || [ ] ; } if ( people && people . length ) { this . personDetailsInternal = people [ 0 ] ; if ( this . _avatarType === 'photo' ) { const image = await getPersonImage ( graph , people [ 0 ] , MgtPerson . config . useContactApis ) ; if ( image ) { this . personDetailsInternal . personImage = image ; this . _fetchedImage = image ; } } } } const defaultPresence = { activity : 'Offline' , availability : 'Offline' , id : null } ; if ( this . showPresence && ! this . personPresence && ! this . _fetchedPresence ) { try { if ( this . personDetailsInternal && this . personDetailsInternal . id ) { const userId = this . personQuery !== 'me' ? this . personDetailsInternal . id : null ; this . _fetchedPresence = await getUserPresence ( graph , userId ) ; } else { this . _fetchedPresence = defaultPresence ; } } catch ( _ ) { this . _fetchedPresence = defaultPresence ; } } } protected returns { string } memberof protected getInitials ( person ? ) { if ( ! person ) { person = this . personDetailsInternal ; } if ( ( person as Contact ) . initials ) { return ( person as Contact ) . initials ; } let initials = '' ; if ( person . givenName ) { initials += person . givenName [ 0 ] . toUpperCase ( ) ; } if ( person . surname ) { initials += person . surname [ 0 ] . toUpperCase ( ) ; } if ( ! initials && person . displayName ) { const name = person . displayName . split ( /\s+/ ) ; for ( let i = 0 ; i < 2 && i < name . length ; i ++ ) { if ( name [ i ] [ 0 ] && this . isLetter ( name [ i ] [ 0 ] ) ) { initials += name [ i ] [ 0 ] . toUpperCase ( ) ; } } } return initials ; } protected param { string } name returns { string } memberof protected getColorFromName ( name ) { const charCodes = name . split ( '' ) . map ( char => char . charCodeAt ( 0 ) ) . join ( '' ) ; const nameInt = parseInt ( charCodes , 10 ) ; const colors = [ 'pinkRed10' , 'red20' , 'red10' , 'orange20' , 'orangeYellow20' , 'green10' , 'green20' , 'cyan20' , 'cyan30' , 'cyanBlue10' , 'cyanBlue20' , 'blue10' , 'blueMagenta30' , 'blueMagenta20' , 'magenta20' , 'magenta10' , 'magentaPink10' , 'orange30' , 'gray30' , 'gray20' ] ; return colors [ nameInt % colors . length ] ; } private getImage ( ) { if ( this . personImage ) { return this . personImage ; } if ( this . _fetchedImage ) { return this . _fetchedImage ; } const person = this . personDetailsInternal ; return person && person . personImage ? person . personImage : null ; } private isLetter ( char ) { try { return char . match ( new RegExp ( '\\p{L}' , 'u' ) ) ; } catch ( e ) { return char . toLowerCase ( ) !== char . toUpperCase ( ) ; } } private getTextFromProperty ( personDetailsInternal , prop ) { if ( ! prop || prop . length === 0 ) { return null ; } const properties = prop . trim ( ) . split ( ',' ) ; let text ; let i = 0 ; while ( ! text && i < properties . length ) { const currentProp = properties [ i ] . trim ( ) ; switch ( currentProp ) { case 'mail' : case 'email' : text = getEmailFromGraphEntity ( personDetailsInternal ) ; break ; default : text = personDetailsInternal [ currentProp ] ; } i ++ ; } return text ; } private isLargeAvatar ( ) { return this . avatarSize === 'large' || ( this . avatarSize === 'auto' && this . view > ViewType . oneline ) ; } private handleMouseClick ( e ) { if ( this . personCardInteraction === PersonCardInteraction . click ) { this . showPersonCard ( ) ; } } private handleKeyDown ( e ) { if ( e ) { if ( e . key === 'Enter' ) { this . showPersonCard ( ) ; } } } private handleMouseEnter ( e ) { clearTimeout ( this . _mouseEnterTimeout ) ; clearTimeout ( this . _mouseLeaveTimeout ) ; if ( this . personCardInteraction !== PersonCardInteraction . hover ) { return ; } this . _mouseEnterTimeout = setTimeout ( this . showPersonCard . bind ( this ) , 500 ) ; } private handleMouseLeave ( e ) { clearTimeout ( this . _mouseEnterTimeout ) ; clearTimeout ( this . _mouseLeaveTimeout ) ; this . _mouseLeaveTimeout = setTimeout ( this . hidePersonCard . bind ( this ) , 500 ) ; } private hidePersonCard ( ) { const flyout = this . flyout ; if ( flyout ) { flyout . close ( ) ; } const personCard = ( this . querySelector ( 'mgt-person-card' ) || this . renderRoot . querySelector ( 'mgt-person-card' ) ) as MgtPersonCard ; if ( personCard ) { personCard . isExpanded = false ; personCard . clearHistory ( ) ; } } private showPersonCard ( ) { if ( ! this . _personCardShouldRender ) { this . _personCardShouldRender = true ; } const flyout = this . flyout ; if ( flyout ) { flyout . open ( ) ; } } }