Skip to content

Commit

Permalink
feat(nav): add animations
Browse files Browse the repository at this point in the history
  • Loading branch information
maxokorokov committed Jul 2, 2020
1 parent 262ac7d commit d82a302
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 23 deletions.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ export {
NgbNavItem,
NgbNavLink,
NgbNavModule,
NgbNavOutlet
NgbNavOutlet,
NgbNavPane
} from './nav/nav.module';
export {
NgbPagination,
Expand Down
119 changes: 105 additions & 14 deletions src/nav/nav-outlet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
import {Component, Input, ViewEncapsulation} from '@angular/core';
import {NgbNav} from './nav';
import {
AfterViewInit,
ChangeDetectorRef,
Component,
Directive,
ElementRef,
Input,
QueryList,
ViewChildren,
ViewEncapsulation
} from '@angular/core';

import {distinctUntilChanged, skip, startWith} from 'rxjs/operators';

import {
ngbNavFadeInNoReflowTransition,
ngbNavFadeInTransition,
ngbNavFadeOutTransition
} from '../util/transition/ngbFadingTransition';
import {ngbRunTransition, NgbTransitionOptions} from '../util/transition/ngbTransition';
import {NgbNav, NgbNavItem} from './nav';

@Directive({
selector: '[ngbNavPane]',
host: {
'[id]': 'item.panelDomId',
'class': 'tab-pane',
'[class.fade]': 'nav.animation',
'[attr.role]': 'role ? role : nav.roles ? "tabpanel" : undefined',
'[attr.aria-labelledby]': 'item.domId'
}
})
export class NgbNavPane {
@Input() item: NgbNavItem;
@Input() nav: NgbNav;
@Input() role: string;

constructor(public elRef: ElementRef<HTMLElement>) {}
}

/**
* The outlet where currently active nav content will be displayed.
Expand All @@ -11,20 +48,19 @@ import {NgbNav} from './nav';
host: {'[class.tab-content]': 'true'},
encapsulation: ViewEncapsulation.None,
template: `
<ng-template ngFor let-item [ngForOf]="nav.items">
<div class="tab-pane"
*ngIf="item.isPanelInDom()"
[id]="item.panelDomId"
[class.active]="item.active"
[attr.role]="paneRole ? paneRole : nav.roles ? 'tabpanel' : undefined"
[attr.aria-labelledby]="item.domId">
<ng-template [ngTemplateOutlet]="item.contentTpl?.templateRef || null"
[ngTemplateOutletContext]="{$implicit: item.active}"></ng-template>
</div>
</ng-template>
<ng-template ngFor let-item [ngForOf]="nav.items">
<div ngbNavPane *ngIf="item.isPanelInDom() || isPanelTransitioning(item)" [item]="item" [nav]="nav" [role]="paneRole">
<ng-template [ngTemplateOutlet]="item.contentTpl?.templateRef || null"
[ngTemplateOutletContext]="{$implicit: item.active || isPanelTransitioning(item)}"></ng-template>
</div>
</ng-template>
`
})
export class NgbNavOutlet {
export class NgbNavOutlet implements AfterViewInit {
private _activePane: NgbNavPane | null = null;

@ViewChildren(NgbNavPane) private _panes: QueryList<NgbNavPane>;

/**
* A role to set on the nav pane
*/
Expand All @@ -34,4 +70,59 @@ export class NgbNavOutlet {
* Reference to the `NgbNav`
*/
@Input('ngbNavOutlet') nav: NgbNav;

constructor(private _cd: ChangeDetectorRef) {}

isPanelTransitioning(item: NgbNavItem) { return this._activePane ?.item === item; }

ngAfterViewInit() {
// initial display
this._activePane = this._getActivePane();
this._activePane ?.elRef.nativeElement.classList.add('show');
this._activePane ?.elRef.nativeElement.classList.add('active');

// this will be emitted for all 3 types of nav changes: .select(), [activeId] or (click)
this.nav.navItemChange$
.pipe(startWith(this._activePane ?.item || null), distinctUntilChanged(), skip(1))
.subscribe(nextItem => {
const options: NgbTransitionOptions<undefined> = {animation: this.nav.animation, runningTransition: 'stop'};

// fading out
if (this._activePane) {
ngbRunTransition(this._activePane.elRef.nativeElement, ngbNavFadeOutTransition, options).subscribe(() => {
const activeItem = this._activePane ?.item;

// next panel we're switching to will only appear in DOM after the change detection is done
// and `this._panes` will be updated
this._cd.detectChanges();

this._activePane = this._getPaneForItem(nextItem);

// fading in
if (this._activePane) {
const fadeInTransition = this.nav.animation ? ngbNavFadeInTransition : ngbNavFadeInNoReflowTransition;
ngbRunTransition(this._activePane.elRef.nativeElement, fadeInTransition, options).subscribe(() => {
if (nextItem) {
nextItem.shown.emit();
this.nav.shown.emit(nextItem.id);
}
});
}

if (activeItem) {
activeItem.hidden.emit();
this.nav.hidden.emit(activeItem.id);
}
});
}
});
}

private _getPaneForItem(item: NgbNavItem | null) {
return this._panes && this._panes.find(pane => pane.item === item) || null;
}

private _getActivePane(): NgbNavPane | null {
return this._panes && this._panes.find(pane => pane.item.active) || null;
}
}
6 changes: 3 additions & 3 deletions src/nav/nav.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';

import {NgbNav, NgbNavContent, NgbNavItem, NgbNavLink} from './nav';
import {NgbNavOutlet} from './nav-outlet';
import {NgbNavOutlet, NgbNavPane} from './nav-outlet';

export {NgbNav, NgbNavContent, NgbNavContentContext, NgbNavItem, NgbNavLink, NgbNavChangeEvent} from './nav';
export {NgbNavOutlet} from './nav-outlet';
export {NgbNavOutlet, NgbNavPane} from './nav-outlet';
export {NgbNavConfig} from './nav-config';

const NGB_NAV_DIRECTIVES = [NgbNavContent, NgbNav, NgbNavItem, NgbNavLink, NgbNavOutlet];
const NGB_NAV_DIRECTIVES = [NgbNavContent, NgbNav, NgbNavItem, NgbNavLink, NgbNavOutlet, NgbNavPane];

@NgModule({declarations: NGB_NAV_DIRECTIVES, exports: NGB_NAV_DIRECTIVES, imports: [CommonModule]})
export class NgbNavModule {
Expand Down

0 comments on commit d82a302

Please sign in to comment.