import { AfterContentInit , ChangeDetectorRef , ChangeDetectionStrategy , Component , ContentChild , ContentChildren , ElementRef , EventEmitter , OnChanges , Input , OnDestroy , Output , QueryList , SimpleChanges , ViewEncapsulation , } from '@angular/core' ; import { coerceBooleanProperty } from '@angular/cdk/coercion' ; import { Platform } from '@angular/cdk/platform' ; import { merge , Observable , Subscription , Subject } from 'rxjs' ; import { startWith } from 'rxjs/operators' ; import { MDCComponent } from '@angular-mdc/web/base' ; import { MdcTabScroller , MdcTabScrollerAlignment } from '@angular-mdc/web/tab-scroller' ; import { MdcTab , MdcTabInteractedEvent , MDC_TAB_BAR_PARENT_COMPONENT } from '@angular-mdc/web/tab' ; import { MDCTabBarFoundation , MDCTabBarAdapter } from '@material/tab-bar' ; export class MdcTabActivatedEvent { constructor ( public source , public index , public tab ) { } } @ Component ( { selector : '[mdcTabBar], mdc-tab-bar' , exportAs : 'mdcTabBar' , host : { 'role' : 'tablist' , 'class' : 'mdc-tab-bar' , '(keydown)' : '_onKeydown($event)' } , template : '<ng-content></ng-content>' , changeDetection : ChangeDetectionStrategy . OnPush , encapsulation : ViewEncapsulation . None , providers : [ { provide : MDC_TAB_BAR_PARENT_COMPONENT , useExisting : MdcTabBar } ] } ) export class MdcTabBar extends MDCComponent < MDCTabBarFoundation > implements AfterContentInit , OnChanges , OnDestroy { /** Emits whenever the component is destroyed. */ private _destroy = new Subject < void > ( ) ; private _initialized : boolean = false ; @ Input ( ) get fade ( ) : boolean { return this . _fade ; } set fade ( value ) { this . _fade = coerceBooleanProperty ( value ) ; this . _syncTabs ( ) ; } private _fade : boolean = false ; @ Input ( ) get stacked ( ) : boolean { return this . _stacked ; } set stacked ( value ) { this . _stacked = coerceBooleanProperty ( value ) ; this . _syncTabs ( ) ; } private _stacked : boolean = false ; @ Input ( ) get fixed ( ) : boolean { return this . _fixed ; } set fixed ( value ) { this . _fixed = coerceBooleanProperty ( value ) ; this . _syncTabs ( ) ; } private _fixed : boolean = false ; @ Input ( ) get align ( ) : MdcTabScrollerAlignment | null { return this . _align ; } set align ( value ) { this . _align = value || 'start' ; this . _syncTabScroller ( ) ; } private _align : MdcTabScrollerAlignment | null = null ; @ Input ( ) get iconIndicator ( ) : string | null { return this . _iconIndicator ; } set iconIndicator ( value ) { this . _iconIndicator = value ; this . _syncTabs ( ) ; } private _iconIndicator : string | null = null ; @ Input ( ) get useAutomaticActivation ( ) : boolean { return this . _useAutomaticActivation ; } set useAutomaticActivation ( value ) { this . _useAutomaticActivation = coerceBooleanProperty ( value ) ; this . _foundation . setUseAutomaticActivation ( this . _useAutomaticActivation ) ; } private _useAutomaticActivation : boolean = true ; @ Input ( ) get activeTabIndex ( ) : number { return this . _activeTabIndex ; } set activeTabIndex ( value ) { if ( this . activeTabIndex !== value ) { this . _activeTabIndex = value ; this . activateTab ( this . activeTabIndex ) ; } } private _activeTabIndex : number = 0 ; @ Input ( ) get focusOnActivate ( ) : boolean { return this . _focusOnActivate ; } set focusOnActivate ( value ) { this . _focusOnActivate = coerceBooleanProperty ( value ) ; this . _syncTabs ( ) ; } private _focusOnActivate : boolean = true ; @ Output ( ) readonly activated : EventEmitter < MdcTabActivatedEvent > = new EventEmitter < MdcTabActivatedEvent > ( ) ; @ ContentChild ( MdcTabScroller , { static : true } ) tabScroller ! : MdcTabScroller ; @ ContentChildren ( MdcTab , { descendants : true } ) tabs ! : QueryList < MdcTab > ; /** Subscription to changes in tabs. */ private _changeSubscription : Subscription | null = null ; /** Subscription to interaction events in tabs. */ private _tabInteractionSubscription : Subscription | null = null ; /** Combined stream of all of the tab interaction events. */ get tabInteractions ( ) : Observable < MdcTabInteractedEvent > { return merge ( ... this . tabs . map ( tab => tab . interacted ) ) ; } getDefaultFoundation ( ) { const adapter = { scrollTo : ( scrollX ) => this . tabScroller . scrollTo ( scrollX ) , incrementScroll : ( scrollXIncrement ) => this . tabScroller . incrementScroll ( scrollXIncrement ) , getScrollPosition : ( ) => this . tabScroller . getScrollPosition ( ) , getScrollContentWidth : ( ) => this . tabScroller . getScrollContentWidth ( ) , getOffsetWidth : ( ) => this . _getHostElement ( ) . offsetWidth , isRTL : ( ) => this . _platform . isBrowser ? window . getComputedStyle ( this . _getHostElement ( ) ) . getPropertyValue ( 'direction' ) === 'rtl' : false , setActiveTab : ( index ) => this . activateTab ( index ) , activateTabAtIndex : ( index , clientRect ? ) => { if ( this . _indexIsInRange ( index ) ) { this . tabs . toArray ( ) [ index ] . activate ( clientRect ) ; } } , deactivateTabAtIndex : ( index ) => { if ( this . _indexIsInRange ( index ) ) { this . tabs . toArray ( ) [ index ] . deactivate ( ) ; } } , focusTabAtIndex : ( index ) => this . tabs . toArray ( ) [ index ] . focus ( ) , getTabIndicatorClientRectAtIndex : ( previousActiveIndex ) => { if ( ! this . _platform . isBrowser ) { return { height : 0 , width : 0 , bottom : 0 , top : 0 , left : 0 , right : 0 } ; } if ( ! this . _indexIsInRange ( previousActiveIndex ) ) { previousActiveIndex = this . activeTabIndex ; } return this . tabs . toArray ( ) [ previousActiveIndex ] . computeIndicatorClientRect ( ) ; } , getTabDimensionsAtIndex : ( index ) => this . tabs . toArray ( ) [ index ] . computeDimensions ( ) , getPreviousActiveTabIndex : ( ) => this . tabs . toArray ( ) . findIndex ( ( _ ) => _ . active ) , getFocusedTabIndex : ( ) => this . _platform . isBrowser ? this . tabs . toArray ( ) . findIndex ( tab => tab . elementRef . nativeElement === document . activeElement ! ) : - 1 , getIndexOfTabById : ( id ) => this . tabs . toArray ( ) . findIndex ( tab => id === tab . id ) , getTabListLength : ( ) => this . tabs . length , notifyTabActivated : ( index ) => this . activated . emit ( { source : this , index : index , tab : this . tabs . toArray ( ) [ index ] } ) } ; return new MDCTabBarFoundation ( adapter ) ; } constructor ( private _platform , private _changeDetectorRef , public elementRef ) { super ( elementRef ) ; } ngOnChanges ( changes ) { if ( ! this . _initialized ) { return ; } if ( changes [ 'align' ] ) { this . _syncTabScroller ( ) ; } } ngAfterContentInit ( ) { this . _initialized = true ; this . _foundation . init ( ) ; this . _changeSubscription = this . tabs . changes . pipe ( startWith ( null ) ) . subscribe ( ( ) => { Promise . resolve ( ) . then ( ( ) => { if ( this . tabs . length ) { this . _syncTabs ( ) ; this . _syncTabScroller ( ) ; this . activateTab ( this . activeTabIndex ) ; this . _resetTabSubscriptions ( ) ; } } ) ; } ) ; } ngOnDestroy ( ) { this . _destroy . next ( ) ; this . _destroy . complete ( ) ; if ( this . _changeSubscription ) { this . _changeSubscription . unsubscribe ( ) ; } this . _dropSubscriptions ( ) ; } private _syncTabs ( ) { if ( ! this . tabs ) { return ; } this . tabs . forEach ( tab => { tab . stacked = this . _stacked ; tab . fixed = this . _fixed ; tab . tabIndicator . fade = this . _fade ; tab . tabIndicator . icon = this . _iconIndicator ; tab . focusOnActivate = this . _focusOnActivate ; } ) ; } private _syncTabScroller ( ) { if ( this . tabScroller ) { this . tabScroller . align = this . align ; } } private _resetTabSubscriptions ( ) { this . _dropSubscriptions ( ) ; this . _listenToTabInteraction ( ) ; } private _dropSubscriptions ( ) { if ( this . _tabInteractionSubscription ) { this . _tabInteractionSubscription . unsubscribe ( ) ; this . _tabInteractionSubscription = null ; } } /** Listens to interaction events on each tab. */ private _listenToTabInteraction ( ) { this . _tabInteractionSubscription = this . tabInteractions . subscribe ( event => { const previousTab = this . getActiveTab ( ) ; if ( previousTab ) { previousTab . tabIndicator . active = false ; } event . detail . tab . tabIndicator . active = true ; this . _foundation . handleTabInteraction ( event as any ) ; } ) ; } /** Activates the tab at the given index */ activateTab ( index ) { if ( ! this . tabs ) { return ; } this . activeTabIndex = index ; if ( this . _platform . isBrowser ) { this . _foundation . activateTab ( index ) ; } this . _changeDetectorRef . markForCheck ( ) ; } /** Scrolls the tab at the given index into view */ scrollIntoView ( index ) { this . _foundation . scrollIntoView ( index ) ; } getActiveTabIndex ( ) { return this . tabs . toArray ( ) . findIndex ( ( _ ) => _ . active ) ; } getActiveTab ( ) { return this . tabs . toArray ( ) . find ( ( _ ) => _ . active ) ; } /** Returns an index for given tab */ getTabIndex ( tab ) { return this . tabs . toArray ( ) . indexOf ( tab ) ; } /** Disable or enable the tab at the given index */ disableTab ( index , disabled ) { if ( ! this . tabs ) { return ; } this . tabs . toArray ( ) [ index ] . disabled = coerceBooleanProperty ( disabled ) ; } _onKeydown ( evt ) { this . _foundation . handleKeyDown ( evt ) ; } private _indexIsInRange ( index ) { return index >= 0 && index < this . tabs . length ; } /** Retrieves the DOM element of the component host. */ private _getHostElement ( ) { return this . elementRef . nativeElement ; } }