import { Renderer2 } from '@angular/core' ; import { ThyGuiderRef } from './guider-ref' ; import { fromEvent , Subscription } from 'rxjs' ; import { RendererFactory2 } from '@angular/core' ; import { ThyGuiderManager } from './guider-manager' ; import { ThyGuiderStep } from './guider.class' ; import { ThyPopover , ThyPopoverConfig , ThyPopoverRef } from 'ngx-tethys/popover' ; import { helpers } from 'ngx-tethys/util' ; import { Overlay } from '@angular/cdk/overlay' ; const pointContainerSize = 28 ; export class ThyGuiderStepRef { private renderer : Renderer2 ; private lastPointerContainer : any ; private lastTargetElement : Element ; private targetElementObserver : Subscription ; private lastTipContainer : any ; private guiderRef : ThyGuiderRef ; private lastPopoverRef : ThyPopoverRef < any > ; constructor ( public step , public stepIndex , private readonly rendererFactory , private popover , private guiderManager , private overlay , private document ) { this . renderer = this . rendererFactory . createRenderer ( null , null ) ; } public show ( guiderRef ) { this . guiderRef = guiderRef ; this . createPoint ( this . step , guiderRef ) ; } public dispose ( ) { this . removeLastPointContainer ( ) ; this . removeTip ( ) ; } private getTargetElement ( step ) { let targetElement ; if ( step . target ) { targetElement = this . document . querySelector ( step . target ) ; } else { targetElement = this . guiderManager . getActiveTarget ( step . key ) ; } return targetElement ; } private createPoint ( step , guiderRef ) { if ( ! this . isTipHasTarget ( step ) ) { this . createTip ( this . step ) ; return ; } const targetElement = this . getTargetElement ( step ) ; if ( helpers . isNull ( targetElement ) ) { throw new Error ( `there is no target called ${ step . target }` ) ; } this . targetElementObserver = fromEvent ( targetElement , 'click' ) . subscribe ( ( ) => { guiderRef . targetClicked ( ) . next ( step ) ; } ) ; const positionValue = targetElement ?. style ?. position ; if ( ! positionValue || positionValue === 'static' ) { this . renderer . setStyle ( targetElement , 'position' , 'relative' ) ; } this . setStyleForPointContainer ( step , targetElement ) ; } private setStyleForPointContainer ( step , targetElement ) { const pointPosition = this . getPointPosition ( step , targetElement ) ; const pointContainer = this . setPointPosition ( pointPosition ) ; this . renderPoint ( targetElement , pointContainer ) ; } private getPointPosition ( step , targetElement ) { const targetElementClientRect = targetElement . getBoundingClientRect ( ) ; const { width : targetElementWidth , height : targetElementHeight } = targetElementClientRect ; const pointOffset = step . pointOffset ; return [ targetElementWidth + pointOffset [ 0 ] , targetElementHeight + pointOffset [ 1 ] ] ; } private setPointPosition ( pointPosition ) { const currentPointContainer = this . renderer . createElement ( 'div' ) ; this . renderer . addClass ( currentPointContainer , 'thy-guider-highlight-container' ) ; if ( this . guiderRef . config . pointClass ) { this . addPointClass ( currentPointContainer , this . guiderRef . config . pointClass ) ; } this . renderer . setStyle ( currentPointContainer , 'position' , 'absolute' ) ; this . renderer . setStyle ( currentPointContainer , 'left' , pointPosition [ 0 ] + 'px' ) ; this . renderer . setStyle ( currentPointContainer , 'top' , pointPosition [ 1 ] + 'px' ) ; this . renderer . setStyle ( currentPointContainer , 'transform' , 'translate(-100%,-100%)' ) ; return currentPointContainer ; } private addPointClass ( el , pointClass ) { if ( helpers . isString ( pointClass ) ) { this . renderer . addClass ( el , pointClass ) ; } if ( helpers . isArray ( pointClass ) ) { pointClass . forEach ( classItem => { this . renderer . addClass ( el , classItem ) ; } ) ; } } private renderPoint ( targetElement , pointContainer ) { this . renderer . appendChild ( targetElement , pointContainer ) ; this . lastPointerContainer = pointContainer ; this . lastTargetElement = targetElement ; this . createTip ( this . step ) ; } private removeLastPointContainer ( ) { if ( this . lastPointerContainer ) { this . renderer . removeChild ( this . document . body , this . lastPointerContainer ) ; this . lastPointerContainer = undefined ; } } private createTip ( step ) { if ( this . isTipHasTarget ( step ) ) { this . tipWithTarget ( step ) ; } else { this . tipWithoutTarget ( step ) ; } } private tipWithoutTarget ( step ) { const position = this . getTipPosition ( step ) ; this . lastPopoverRef = this . popover . open ( this . guiderRef . config . hintComponent , { origin : null , originPosition : { x : position [ 0 ] , y : position [ 1 ] } , originActiveClass : '' , panelClass : this . guiderRef . config . hintClass || '' , backdropClosable : false , hasBackdrop : false , manualClosure : true , initialState : { guiderRef : this . guiderRef , stepRef : this } , scrollStrategy : this . overlay . scrollStrategies . block ( ) } ) ; } private getTipPosition ( step ) { if ( Array . isArray ( step . target ) ) { return step . target ; } return this . guiderRef . config . defaultPosition ; } private createTipContainer ( ) { const tipContainer = this . renderer . createElement ( 'div' ) ; this . renderer . addClass ( tipContainer , 'thy-guider-content-container' ) ; this . renderer . setStyle ( tipContainer , 'position' , 'absolute' ) ; this . renderer . setStyle ( tipContainer , 'top' , '0px' ) ; this . renderer . setStyle ( tipContainer , 'right' , '0px' ) ; this . renderer . setStyle ( tipContainer , 'bottom' , '0px' ) ; this . renderer . setStyle ( tipContainer , 'left' , '0px' ) ; return tipContainer ; } private tipWithTarget ( step ) { let targetElement ; if ( step . target ) { targetElement = this . document . querySelector ( step . target ) ; } else { targetElement = this . guiderManager . getActiveTarget ( step . key ) ; } const hintContainer = this . createTipContainer ( ) ; this . renderer . appendChild ( targetElement , hintContainer ) ; this . lastTipContainer = hintContainer ; const popoverConfig = { origin : hintContainer , placement : step . hintPlacement , panelClass : this . guiderRef . config . hintClass || '' , backdropClosable : false , hasBackdrop : false , manualClosure : true , initialState : { guiderRef : this . guiderRef , stepRef : this } , scrollStrategy : this . overlay . scrollStrategies . block ( ) } as ThyPopoverConfig < any > ; const pointPosition = this . getPointPosition ( step , targetElement ) ; const hintOffset = this . getTipOffset ( step , pointPosition , targetElement ) ; if ( hintOffset ) { popoverConfig . offset = hintOffset ; } this . lastPopoverRef = this . popover . open ( this . guiderRef . config . hintComponent , popoverConfig ) ; } private getTipOffset ( step , pointPosition , targetElement ) { const hintPlacement = step . hintPlacement ; const targetElementClientRect = targetElement . getBoundingClientRect ( ) ; const { width : targetElementWidth , height : targetElementHeight } = targetElementClientRect ; let hintOffset = step . hintOffset || 0 ; const pointXAxisOffset = pointPosition [ 0 ] ; const pointYAxisOffset = pointPosition [ 1 ] ; if ( hintPlacement . startsWith ( 'top' ) ) { if ( pointYAxisOffset < pointContainerSize ) { hintOffset = hintOffset + Math . abs ( pointYAxisOffset ) + pointContainerSize ; } } else if ( hintPlacement . startsWith ( 'bottom' ) ) { if ( pointYAxisOffset > targetElementHeight ) { hintOffset = hintOffset + ( pointYAxisOffset - targetElementHeight ) + 10 ; } } else if ( hintPlacement . startsWith ( 'left' ) ) { if ( pointXAxisOffset < 0 ) { hintOffset = hintOffset + Math . abs ( pointXAxisOffset ) + pointContainerSize ; } } else if ( hintPlacement . startsWith ( 'right' ) ) { if ( pointXAxisOffset > targetElementWidth ) { hintOffset = hintOffset + ( pointXAxisOffset - targetElementWidth ) + 10 ; } } return hintOffset ; } private removeTip ( ) { if ( this . lastPopoverRef ) { this . lastPopoverRef . close ( ) ; this . lastPopoverRef = undefined ; } if ( this . lastTipContainer ) { this . renderer . removeChild ( this . document . body , this . lastTipContainer ) ; this . lastTipContainer = undefined ; } if ( this . lastTargetElement && this . targetElementObserver ) { this . targetElementObserver . unsubscribe ( ) ; this . lastTargetElement = undefined ; this . targetElementObserver = undefined ; } } private isTipHasTarget ( step ) { if ( step . target ) { return ! Array . isArray ( step . target ) ; } else { return ! ! this . guiderManager . getActiveTarget ( step . key ) ; } } }