import { ElementRef , Injectable , OnDestroy } from '@angular/core' ; import { ShContextMenuComponent } from './sh-context-menu.component' ; import { ConnectionPositionPair , FlexibleConnectedPositionStrategy , GlobalPositionStrategy , Overlay , ScrollStrategy } from '@angular/cdk/overlay' ; import { TemplatePortal } from '@angular/cdk/portal' ; import { ShContextMenuEvent , ShContextSubMenuEvent } from './sh-context-menu.models' ; import { OverlayRef } from '@angular/cdk/overlay' ; import { fromEvent , Subscription } from 'rxjs' ; @ Injectable ( ) export class ShContextMenuService implements OnDestroy { activeOverlays : OverlayRef [ ] = [ ] ; subs : Subscription ; activeMenu : ShContextMenuComponent ; anchorElement : HTMLElement ; constructor ( private overlay ) { } openMenu ( ctxEvent ) { this . closeCurrentOverlays ( ) ; const { menu , mouseEvent , data } = ctxEvent ; this . activeMenu = menu ; this . anchorElement = this . createAnchorElement ( ) ; const scrollStrategy = this . buildScrollStrategy ( ) ; const positionStrategy = this . buildPositionStrategy ( this . anchorElement , mouseEvent ) ; this . attachContextToItems ( menu , data ) ; const overlayRef = this . createAndAttachOverlay ( positionStrategy , scrollStrategy , menu , true ) ; this . attachOverlayRef ( menu , overlayRef ) ; this . registerDetachEvents ( overlayRef ) ; } openSubMenu ( ctxEvent ) { const { menu , mouseEvent , targetElement , data , parentMenu } = ctxEvent ; mouseEvent . preventDefault ( ) ; mouseEvent . stopPropagation ( ) ; const scrollStrategy = this . buildScrollStrategy ( ) ; const positionStrategy = this . buildPositionStrategyForSubMenu ( targetElement ) ; const overlayRef = this . createAndAttachOverlay ( positionStrategy , scrollStrategy , menu , false ) ; this . attachContextToItems ( menu , data ) ; this . attachThisContext ( menu , parentMenu ) ; this . attachOverlayRef ( menu , overlayRef ) ; } destroy ( ) { this . closeCurrentOverlays ( ) ; this . subs . unsubscribe ( ) ; } ngOnDestroy ( ) { this . destroy ( ) ; } closeSubMenus ( menu ) { const itemsWithSubMenus = menu . menuItems . filter ( i => ! ! i . subMenu && ! ! i . subMenu . overlayRef ) ; if ( itemsWithSubMenus . length ) { itemsWithSubMenus . forEach ( sm => this . closeSubMenus ( sm . subMenu ) ) ; const overlayRefs = itemsWithSubMenus . map ( i => i . subMenu . overlayRef ) ; overlayRefs . forEach ( r => r . dispose ( ) ) ; } } private registerDetachEvents ( overlayRef ) { this . subs = overlayRef . backdropClick ( ) . subscribe ( this . closeCurrentOverlays . bind ( this ) ) ; this . subs . add ( overlayRef . detachments ( ) . subscribe ( this . closeCurrentOverlays . bind ( this ) ) ) ; } private createAndAttachOverlay ( positionStrategy , scrollStrategy , menu , hasBackdrop = true ) { const overlayRef = this . overlay . create ( { positionStrategy , scrollStrategy , hasBackdrop : hasBackdrop , backdropClass : 'sh-backdrop' } ) ; const menuPortal = new TemplatePortal ( menu . menuTemplate , menu . menuContainer ) ; overlayRef . attach ( menuPortal ) ; this . activeOverlays . push ( overlayRef ) ; return overlayRef ; } private buildScrollStrategy ( ) { return this . overlay . scrollStrategies . reposition ( { autoClose : true } ) ; } private buildPositionStrategy ( ele , event ) { const { x , y } = event ; return this . overlay . position ( ) . flexibleConnectedTo ( ele ) . withDefaultOffsetX ( x ) . withDefaultOffsetY ( y ) . withPositions ( this . buildPositions ( ) ) . withFlexibleDimensions ( false ) . withPush ( true ) ; } private buildPositionStrategyForSubMenu ( elm ) { return this . overlay . position ( ) . flexibleConnectedTo ( elm ) . withPositions ( this . buildSubMenuPositions ( ) ) . withFlexibleDimensions ( false ) . withPush ( true ) ; } private closeCurrentOverlays ( ) { if ( this . anchorElement && this . anchorElement . parentNode ) { this . anchorElement . parentNode . removeChild ( this . anchorElement ) ; } this . activeOverlays . forEach ( o => { o . detach ( ) ; o . dispose ( ) ; } ) ; this . activeOverlays = [ ] ; if ( this . activeMenu ) { this . activeMenu . close ( ) ; } } private attachContextToItems ( menu , data ) { menu . menuItems . forEach ( i => ( i . context . $implicit = data ) ) ; } private attachThisContext ( menu , parentMenu ) { menu . thisContext = parentMenu . thisContext ; } private attachOverlayRef ( menu , overlayRef ) { menu . overlayRef = overlayRef ; } private createAnchorElement ( ) { const div = document . createElement ( 'div' ) ; div . style . position = 'absolute' ; div . style . top = '0' ; div . style . bottom = '0' ; div . style . left = '0' ; div . style . right = '0' ; document . body . appendChild ( div ) ; return div ; } private buildSubMenuPositions ( ) { return [ { originX : 'end' , originY : 'top' , overlayX : 'start' , overlayY : 'top' } , { originX : 'start' , originY : 'top' , overlayX : 'end' , overlayY : 'top' } , { originX : 'end' , originY : 'bottom' , overlayX : 'start' , overlayY : 'bottom' } , { originX : 'start' , originY : 'bottom' , overlayX : 'end' , overlayY : 'bottom' } ] ; } private buildPositions ( ) { return [ { originX : 'start' , originY : 'top' , overlayX : 'start' , overlayY : 'top' } , { originX : 'start' , originY : 'top' , overlayX : 'end' , overlayY : 'top' } , { originX : 'start' , originY : 'top' , overlayX : 'start' , overlayY : 'bottom' } , { originX : 'start' , originY : 'top' , overlayX : 'end' , overlayY : 'bottom' } ] ; } }