import { Injectable , OnDestroy } from '@angular/core' ; import { filter , takeUntil } from 'rxjs/operators' ; import { Subject } from 'rxjs' ; import { ActionDirection , DashboardService } from '../dashboard.service' ; import { DashboardWidgetComponent } from '../widget/dashboard-widget.component' ; import { DashboardGrabHandleDirective } from './grab-handle.directive' ; @ Injectable ( ) export class DashboardGrabHandleService implements OnDestroy { /** Self-registered drag handles in the dashboard. */ private _handles : DashboardGrabHandleDirective [ ] = [ ] ; /** Automatically unsubscribe from all observables when destroyed */ private _onDestroy = new Subject < void > ( ) ; constructor ( private _dashboard ) { _dashboard . layout$ . pipe ( takeUntil ( this . _onDestroy ) , filter ( ( ) => ! this . _dashboard . isGrabbing$ . value ) ) . subscribe ( ( ) => this . setFirstItemFocusable ( ) ) ; } /** Perform unsubscriptions */ ngOnDestroy ( ) { this . _onDestroy . next ( ) ; this . _onDestroy . complete ( ) ; } /** Register a new grab handle. */ addHandle ( handle ) { this . _handles = this . getHandlesInOrder ( [ ... this . _handles , handle ] ) ; requestAnimationFrame ( ( ) => this . ensureFocusable ( ) ) ; } /** Unregister a removed grab handle. */ removeHandle ( handle ) { this . _handles = this . _handles . filter ( h => h !== handle ) ; this . ensureFocusable ( ) ; } /** Make the first visual item in the list focusable */ setFirstItemFocusable ( ) { this . setItemFocus ( 0 , false ) ; } /** Set an item at a given index focused */ setItemFocus ( index , focusElement = true ) { if ( ! this . _handles || this . _handles . length === 0 ) { return ; } if ( index < 0 ) { return this . setItemFocus ( 0 ) ; } if ( index > this . _handles . length - 1 ) { return this . setItemFocus ( this . _handles . length - 1 ) ; } this . getHandlesInOrder ( ) . forEach ( ( handle , idx ) => idx === index ? handle . focus ( focusElement ) : handle . blur ( ) ) ; this . ensureFocusable ( ) ; } /** Focus the previous grab handle */ setPreviousItemFocus ( handle ) { this . setItemFocus ( this . getHandleIndex ( handle ) - 1 ) ; } /** Focus the next grab handle */ setNextItemFocus ( handle ) { this . setItemFocus ( this . getHandleIndex ( handle ) + 1 ) ; } /** Focus the grab handle on the widget above */ setSiblingItemFocus ( widget , direction ) { const target = this . _dashboard . getSurroundingWidgets ( widget , direction ) . map ( _widget => this . _handles . find ( handle => handle . widget === _widget ) ) . filter ( handle => ! ! handle ) . reduce ( ( handle , current ) => ! handle || current . widget . getColumn ( ) > handle . widget . getColumn ( ) ? current : handle , null ) ; if ( ! target ) { return ; } const index = this . getHandleIndex ( target ) ; this . setItemFocus ( index ) ; } /** Get handles in the order they appear rather than the order they are in the DOM */ getHandlesInOrder ( handles = this . _handles ) { const widgets = this . _dashboard . getWidgetsByOrder ( ) ; return handles . sort ( ( handleOne , handleTwo ) => widgets . indexOf ( handleOne . widget ) - widgets . indexOf ( handleTwo . widget ) ) ; } private getHandleIndex ( handle ) { return this . getHandlesInOrder ( ) . findIndex ( _handle => _handle === handle ) ; } /** If the current focusable handle is removed we need to make another one focusable */ private ensureFocusable ( ) { if ( ! this . _handles . find ( handle => handle . tabIndex === 0 ) ) { this . setFirstItemFocusable ( ) ; } } }