ng-ytl-zorro-antd
Version:
An enterprise-class UI components based on Ant Design and Angular
353 lines (307 loc) • 11.6 kB
text/typescript
/** code from https://github.com/angular/material2 */
import { Direction, Directionality } from '@angular/cdk/bidi';
import {
AfterContentChecked,
AfterContentInit,
Component,
ContentChild,
ContentChildren,
ElementRef,
HostBinding,
Input,
NgZone,
Optional,
QueryList,
Renderer2,
TemplateRef,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { fromEvent } from 'rxjs/observable/fromEvent';
import { merge } from 'rxjs/observable/merge';
import { of as observableOf } from 'rxjs/observable/of';
import { auditTime } from 'rxjs/operators/auditTime';
import { startWith } from 'rxjs/operators/startWith';
import { toBoolean } from '../util/convert';
import { NzTabLabelDirective } from './nz-tab-label.directive';
import { NzTabsInkBarDirective } from './nz-tabs-ink-bar.directive';
const EXAGGERATED_OVERSCROLL = 64;
export type ScrollDirection = 'after' | 'before';
/** duplicated defined https://github.com/angular/angular-cli/issues/2034 **/
export type NzTabPositionMode = 'horizontal' | 'vertical';
export class NzTabsNavComponent implements AfterContentChecked, AfterContentInit {
private _animated = true;
private _hideBar = false;
private _showPagination = true;
_showPaginationControls = false;
_disableScrollAfter = true;
_disableScrollBefore = true;
_scrollDistance = 0;
_selectedIndexChanged = false;
_realignInkBar: Subscription | null = null;
_tabLabelCount: number;
_scrollDistanceChanged: boolean;
_selectedIndex = 0;
_tabPositionMode: NzTabPositionMode = 'horizontal';
nzSize = 'default';
_type = 'line';
set nzAnimated(value: boolean) {
this._animated = toBoolean(value);
}
get nzAnimated(): boolean {
return this._animated;
}
set nzHideBar(value: boolean) {
this._hideBar = toBoolean(value);
}
get nzHideBar(): boolean {
return this._hideBar;
}
set nzType(value: string) {
this._type = value;
if (this._type !== 'line') {
this._inkBar.setDisplay('none');
} else {
this._inkBar.setDisplay('block');
}
}
get nzType(): string {
return this._type;
}
_tabBarExtraContent: TemplateRef<void>;
_labelWrappers: QueryList<NzTabLabelDirective>;
_inkBar: NzTabsInkBarDirective;
_tabListContainer: ElementRef;
_tabList: ElementRef;
set nzShowPagination(value: boolean) {
this._showPagination = toBoolean(value);
}
get nzShowPagination(): boolean {
return this._showPagination;
}
set nzPositionMode(value: NzTabPositionMode) {
this._tabPositionMode = value;
this._alignInkBarToSelectedTab();
if (this.nzShowPagination) {
this._updatePagination();
}
}
get nzPositionMode(): NzTabPositionMode {
return this._tabPositionMode;
}
set selectedIndex(value: number) {
this._selectedIndexChanged = this._selectedIndex !== value;
this._selectedIndex = value;
}
get selectedIndex(): number {
return this._selectedIndex;
}
constructor(public _elementRef: ElementRef,
private _ngZone: NgZone,
private _renderer: Renderer2,
private _dir: Directionality) {
}
_onContentChanges(): void {
if (this.nzShowPagination) {
this._updatePagination();
}
this._alignInkBarToSelectedTab();
}
_scrollHeader(scrollDir: ScrollDirection): void {
// Move the scroll distance one-third the length of the tab list's viewport.
this.scrollDistance += (scrollDir === 'before' ? -1 : 1) * this.viewWidthHeightPix / 3;
}
ngAfterContentChecked(): void {
if (this._tabLabelCount !== this._labelWrappers.length) {
if (this.nzShowPagination) {
this._updatePagination();
}
this._tabLabelCount = this._labelWrappers.length;
}
if (this._selectedIndexChanged) {
this._scrollToLabel(this._selectedIndex);
if (this.nzShowPagination) {
this._checkScrollingControls();
}
this._alignInkBarToSelectedTab();
this._selectedIndexChanged = false;
}
if (this._scrollDistanceChanged) {
if (this.nzShowPagination) {
this._updateTabScrollPosition();
}
this._scrollDistanceChanged = false;
}
}
ngAfterContentInit(): void {
this._realignInkBar = this._ngZone.runOutsideAngular(() => {
const dirChange = this._dir ? this._dir.change : observableOf(null);
const resize = typeof window !== 'undefined' ?
fromEvent(window, 'resize').pipe(auditTime(10)) :
observableOf(null);
return merge(dirChange, resize).pipe(startWith(null)).subscribe(() => {
if (this.nzShowPagination) {
this._updatePagination();
}
this._alignInkBarToSelectedTab();
});
});
}
_updateTabScrollPosition(): void {
const scrollDistance = this.scrollDistance;
if (this.nzPositionMode === 'horizontal') {
const translateX = this._getLayoutDirection() === 'ltr' ? -scrollDistance : scrollDistance;
this._renderer.setStyle(this._tabList.nativeElement, 'transform',
`translate3d(${translateX}px, 0, 0)`);
} else {
this._renderer.setStyle(this._tabList.nativeElement, 'transform',
`translate3d(0,${-scrollDistance}px, 0)`);
}
}
_updatePagination(): void {
this._checkPaginationEnabled();
this._checkScrollingControls();
this._updateTabScrollPosition();
}
_checkPaginationEnabled(): void {
this._showPaginationControls =
this.tabListScrollWidthHeightPix > this.elementRefOffSetWidthHeight;
if (!this._showPaginationControls) {
this.scrollDistance = 0;
}
}
_scrollToLabel(labelIndex: number): void {
const selectedLabel = this._labelWrappers
? this._labelWrappers.toArray()[ labelIndex ]
: null;
if (!selectedLabel) {
return;
}
// The view length is the visible width of the tab labels.
let labelBeforePos: number;
let labelAfterPos: number;
if (this.nzPositionMode === 'horizontal') {
if (this._getLayoutDirection() === 'ltr') {
labelBeforePos = selectedLabel.getOffsetLeft();
labelAfterPos = labelBeforePos + selectedLabel.getOffsetWidth();
} else {
labelAfterPos = this._tabList.nativeElement.offsetWidth - selectedLabel.getOffsetLeft();
labelBeforePos = labelAfterPos - selectedLabel.getOffsetWidth();
}
} else {
labelBeforePos = selectedLabel.getOffsetTop();
labelAfterPos = labelBeforePos + selectedLabel.getOffsetHeight();
}
const beforeVisiblePos = this.scrollDistance;
const afterVisiblePos = this.scrollDistance + this.viewWidthHeightPix;
if (labelBeforePos < beforeVisiblePos) {
// Scroll header to move label to the before direction
this.scrollDistance -= beforeVisiblePos - labelBeforePos + EXAGGERATED_OVERSCROLL;
} else if (labelAfterPos > afterVisiblePos) {
// Scroll header to move label to the after direction
this.scrollDistance += labelAfterPos - afterVisiblePos + EXAGGERATED_OVERSCROLL;
}
}
_checkScrollingControls(): void {
// Check if the pagination arrows should be activated.
this._disableScrollBefore = this.scrollDistance === 0;
this._disableScrollAfter = this.scrollDistance === this._getMaxScrollDistance();
}
/**
* Determines what is the maximum length in pixels that can be set for the scroll distance. This
* is equal to the difference in width between the tab list container and tab header container.
*
* This is an expensive call that forces a layout reflow to compute box and scroll metrics and
* should be called sparingly.
*/
_getMaxScrollDistance(): number {
return (this.tabListScrollWidthHeightPix - this.viewWidthHeightPix) || 0;
}
/** Sets the distance in pixels that the tab header should be transformed in the X-axis. */
set scrollDistance(v: number) {
this._scrollDistance = Math.max(0, Math.min(this._getMaxScrollDistance(), v));
// Mark that the scroll distance has changed so that after the view is checked, the CSS
// transformation can move the header.
this._scrollDistanceChanged = true;
this._checkScrollingControls();
}
get scrollDistance(): number {
return this._scrollDistance;
}
get viewWidthHeightPix(): number {
let PAGINATION_PIX = 0;
if (this._showPaginationControls) {
PAGINATION_PIX = 64;
}
if (this.nzPositionMode === 'horizontal') {
return this._tabListContainer.nativeElement.offsetWidth - PAGINATION_PIX;
} else {
return this._tabListContainer.nativeElement.offsetHeight - PAGINATION_PIX;
}
}
get tabListScrollWidthHeightPix(): number {
if (this.nzPositionMode === 'horizontal') {
return this._tabList.nativeElement.scrollWidth;
} else {
return this._tabList.nativeElement.scrollHeight;
}
}
get elementRefOffSetWidthHeight(): number {
if (this.nzPositionMode === 'horizontal') {
return this._elementRef.nativeElement.offsetWidth;
} else {
return this._elementRef.nativeElement.offsetHeight;
}
}
_getLayoutDirection(): Direction {
return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
}
_alignInkBarToSelectedTab(): void {
if (this.nzType === 'line') {
const selectedLabelWrapper = this._labelWrappers && this._labelWrappers.length
? this._labelWrappers.toArray()[ this.selectedIndex ].elementRef.nativeElement
: null;
if (this._inkBar) {
this._inkBar.alignToElement(selectedLabelWrapper);
}
}
}
}