293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
import {
|
|
AfterViewInit,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
ContentChildren,
|
|
ElementRef, EventEmitter,
|
|
Input, OnDestroy, Output,
|
|
QueryList,
|
|
ViewChild,
|
|
Inject,
|
|
PLATFORM_ID
|
|
} from "@angular/core";
|
|
import {SliderTabComponent} from "./slider-tab.component";
|
|
import {ActivatedRoute, Router} from "@angular/router";
|
|
import {Subscription} from "rxjs";
|
|
import {isPlatformServer} from "@angular/common";
|
|
import Timeout = NodeJS.Timeout;
|
|
|
|
declare var UIkit;
|
|
|
|
@Component({
|
|
selector: 'slider-tabs',
|
|
template: `
|
|
<div #sliderElement class="uk-position-relative" [class.uk-slider]="position === 'horizontal'"
|
|
[ngClass]="customClass">
|
|
<div [class.uk-slider-container-tabs]="position === 'horizontal'" [class.uk-border-bottom]="border && position === 'horizontal'" [ngClass]="containerClass">
|
|
<ul #tabsElement [class.uk-flex-nowrap]="position === 'horizontal'"
|
|
[class.uk-slider-items]="position === 'horizontal'"
|
|
[class.uk-tab-left]="position === 'left'" [class.uk-tab-right]="position === 'right'"
|
|
[attr.uk-switcher]="type === 'static'?('connect:' + connect):null"
|
|
[ngClass]="'uk-flex-' + flexPosition + ' ' + tabsClass">
|
|
<ng-container *ngIf="type === 'static'">
|
|
<li *ngFor="let tab of leftTabs" [ngStyle]="" [style.max-width]="(position === 'horizontal')?'50%':null" class="uk-text-capitalize uk-text-truncate uk-display-block">
|
|
<a>
|
|
<span *ngIf="tab.title">{{tab.title}}</span>
|
|
<ng-container *ngTemplateOutlet="tab.tabTemplate"></ng-container>
|
|
</a>
|
|
</li>
|
|
<li *ngFor="let tab of rightTabs; let i=index;" [style.max-width]="(position === 'horizontal')?'50%':null" [ngClass]="i === 0?'uk-flex-1 uk-flex uk-flex-right':''"
|
|
class="uk-text-capitalize uk-text-truncate uk-display-block">
|
|
<a [ngClass]="tab.customClass">
|
|
<span *ngIf="tab.title">{{tab.title}}</span>
|
|
<ng-container *ngTemplateOutlet="tab.tabTemplate"></ng-container>
|
|
</a>
|
|
</li>
|
|
</ng-container>
|
|
<ng-container *ngIf="type === 'dynamic'">
|
|
<li *ngFor="let tab of leftTabs; let i=index;" [class.uk-active]="tab.active" [style.max-width]="(position === 'horizontal')?'50%':null">
|
|
<a [routerLink]="tab.routerLink" [queryParams]="tab.queryParams" [ngClass]="tab.customClass" [relativeTo]="tab.relativeTo"
|
|
(click)="showActive(i)"
|
|
class="uk-text-capitalize uk-text-truncate uk-display-block">
|
|
<span *ngIf="tab.title">{{tab.title}}</span>
|
|
<ng-container *ngTemplateOutlet="tab.tabTemplate"></ng-container>
|
|
</a>
|
|
</li>
|
|
<li *ngFor="let tab of rightTabs; let i=index;" [style.max-width]="(position === 'horizontal')?'50%':null" [class.uk-active]="tab.active"
|
|
[ngClass]="i === 0?'uk-flex-1 uk-flex uk-flex-right':''">
|
|
<a [routerLink]="tab.routerLink" [queryParams]="tab.queryParams" [ngClass]="tab.customClass" [relativeTo]="tab.relativeTo"
|
|
(click)="showActive(i)"
|
|
class="uk-text-capitalize uk-text-truncate uk-display-block">
|
|
<span *ngIf="tab.title">{{tab.title}}</span>
|
|
<ng-container *ngTemplateOutlet="tab.tabTemplate"></ng-container>
|
|
</a>
|
|
</li>
|
|
</ng-container>
|
|
<ng-container *ngIf="type === 'scrollable'">
|
|
<li *ngFor="let tab of leftTabs" [style.max-width]="(position === 'horizontal')?'50%':null" class="uk-text-capitalize uk-text-truncate uk-display-block" [class.uk-active]="tab.active">
|
|
<a routerLink="./" [fragment]="tab.id" queryParamsHandling="merge" [ngClass]="tab.customClass">
|
|
<span *ngIf="tab.title">{{tab.title}}</span>
|
|
<ng-container *ngTemplateOutlet="tab.tabTemplate"></ng-container>
|
|
</a>
|
|
</li>
|
|
<li *ngFor="let tab of rightTabs; let i=index;" [style.max-width]="(position === 'horizontal')?'50%':null" class="uk-text-capitalize uk-text-truncate uk-display-block"
|
|
[ngClass]="i === 0?'uk-flex-1 uk-flex uk-flex-right':''"
|
|
[class.uk-active]="tab.active">
|
|
<a routerLink="./" [fragment]="tab.id" queryParamsHandling="merge" [ngClass]="tab.customClass">
|
|
<span *ngIf="tab.title">{{tab.title}}</span>
|
|
<ng-container *ngTemplateOutlet="tab.tabTemplate"></ng-container>
|
|
</a>
|
|
</li>
|
|
</ng-container>
|
|
</ul>
|
|
</div>
|
|
<ng-container *ngIf="!isServer">
|
|
<slider-arrow *ngIf="position === 'horizontal' && arrows" type="previous"></slider-arrow>
|
|
<slider-arrow *ngIf="position === 'horizontal' && arrows" type="next"></slider-arrow>
|
|
</ng-container>
|
|
</div>
|
|
`,
|
|
})
|
|
export class SliderTabsComponent implements AfterViewInit, OnDestroy {
|
|
/**
|
|
* Type of tabs:
|
|
* Static = Uikit tabs with @connect class or selector
|
|
* Dynamic = Active is defined by tabComponent's active Input
|
|
* Scrollable = Active is defined by the active fragment of URL and position of scroll
|
|
* */
|
|
@Input()
|
|
public type: 'static' | 'dynamic' | 'scrollable' = 'static';
|
|
/**
|
|
* Connect selector in static type. Default: .uk-switcher
|
|
* */
|
|
@Input()
|
|
public connect = '.uk-switcher';
|
|
/**
|
|
* Threshold between 0.0 to 1.0 for Intersection Observer
|
|
* */
|
|
@Input()
|
|
public scrollThreshold = 0.1;
|
|
/**
|
|
* Tabs view: Horizontal is the default.
|
|
* */
|
|
@Input()
|
|
public position: 'horizontal' | 'left' | 'right' = 'horizontal';
|
|
/**
|
|
* Tabs flex position: Left is the default.
|
|
* */
|
|
@Input()
|
|
public flexPosition: 'center' | 'left' | 'right' = 'left';
|
|
/**
|
|
* Set a class for the container
|
|
* */
|
|
@Input()
|
|
public containerClass: string;
|
|
/**
|
|
* Set a class above tabs
|
|
* */
|
|
@Input()
|
|
public customClass: string;
|
|
/**
|
|
* Tabs class
|
|
* */
|
|
@Input()
|
|
public tabsClass: string = 'uk-tab';
|
|
@Input()
|
|
public border: boolean = true;
|
|
@Input()
|
|
public arrows: boolean = true;
|
|
@ContentChildren(SliderTabComponent) tabs: QueryList<SliderTabComponent>;
|
|
@ViewChild('sliderElement') sliderElement: ElementRef;
|
|
@ViewChild('tabsElement') tabsElement: ElementRef;
|
|
private slider;
|
|
/**
|
|
* Notify regarding new active element
|
|
* */
|
|
@Output() activeEmitter: EventEmitter<string> = new EventEmitter<string>();
|
|
private activeIndex: number = 0;
|
|
private subscriptions: any[] = [];
|
|
private observer: IntersectionObserver;
|
|
private timeout: Timeout;
|
|
isServer: boolean;
|
|
|
|
constructor(private route: ActivatedRoute,
|
|
private router: Router,
|
|
private cdr: ChangeDetectorRef,
|
|
@Inject(PLATFORM_ID) private platform: any) {
|
|
this.isServer = isPlatformServer(this.platform);
|
|
}
|
|
|
|
ngAfterViewInit() {
|
|
this.initTabs();
|
|
this.tabs.changes.subscribe(() => {
|
|
this.initTabs();
|
|
});
|
|
}
|
|
|
|
public initTabs() {
|
|
if (typeof document !== 'undefined' && this.tabs.length > 0) {
|
|
setTimeout(() => {
|
|
if (this.position === 'horizontal') {
|
|
this.slider = UIkit.slider(this.sliderElement.nativeElement, {finite: true});
|
|
this.slider.clsActive = 'uk-slider-active';
|
|
this.slider.updateActiveClasses();
|
|
this.slider.slides.forEach((item, index) => {
|
|
if(!this.tabs.get(index).active) {
|
|
item.classList.remove('uk-active');
|
|
}
|
|
});
|
|
if (this.type === 'static') {
|
|
let tabs = UIkit.switcher(this.tabsElement.nativeElement, {connect: this.connect});
|
|
tabs.show(this.activeIndex);
|
|
if (this.connect.includes('#')) {
|
|
this.scrollToStart();
|
|
}
|
|
} else if(this.type =='dynamic') {
|
|
this.activeIndex = this.tabs.toArray().findIndex(tab => tab.active);
|
|
this.slider.show(this.activeIndex);
|
|
} else if (this.type === 'scrollable') {
|
|
this.scrollable(this.slider);
|
|
}
|
|
} else {
|
|
this.scrollable();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private scrollToStart() {
|
|
this.subscriptions.push(UIkit.util.on(this.connect, 'shown', (event): void => {
|
|
let index = event.detail[0].index();
|
|
if (index !== this.activeIndex) {
|
|
this.activeIndex = index;
|
|
this.router.navigate(['./'], {relativeTo: this.route, fragment: this.connect.replace('#', ''), queryParamsHandling: "merge"});
|
|
}
|
|
}));
|
|
}
|
|
|
|
private scrollable(slider = null) {
|
|
this.activeFragment(this.route.snapshot.fragment, slider);
|
|
this.subscriptions.push(this.route.fragment.subscribe(fragment => {
|
|
this.activeFragment(fragment, slider);
|
|
}));
|
|
this.setObserver();
|
|
}
|
|
|
|
private setObserver() {
|
|
if (this.observer) {
|
|
this.observer.disconnect();
|
|
}
|
|
this.observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
if (this.timeout) {
|
|
clearTimeout(this.timeout);
|
|
}
|
|
this.timeout = setTimeout(() => {
|
|
this.router.navigate(['./'], {
|
|
fragment: entry.target.id,
|
|
relativeTo: this.route,
|
|
state: {disableScroll: true},
|
|
queryParamsHandling: 'merge'
|
|
});
|
|
}, 200);
|
|
}
|
|
});
|
|
}, {threshold: 0.1});
|
|
this.tabs.forEach(tab => {
|
|
let element = document.getElementById(tab.id);
|
|
if (element) {
|
|
this.observer.observe(element);
|
|
}
|
|
});
|
|
}
|
|
|
|
public showActive(index) {
|
|
this.activeIndex = index;
|
|
this.activeEmitter.emit(this.tabs.get(this.activeIndex).id);
|
|
if(this.slider) {
|
|
this.slider.show(this.activeIndex);
|
|
}
|
|
}
|
|
|
|
private activeFragment(fragment, slider) {
|
|
let index = 0;
|
|
if (fragment) {
|
|
index = this.tabs.toArray().findIndex(item => item.id == fragment);
|
|
}
|
|
if (slider) {
|
|
slider.show(index);
|
|
}
|
|
this.tabs.forEach((tab, i) => {
|
|
if (index === i) {
|
|
tab.active = true;
|
|
this.activeEmitter.emit(tab.id);
|
|
} else {
|
|
tab.active = false;
|
|
}
|
|
});
|
|
this.cdr.detectChanges();
|
|
}
|
|
|
|
get leftTabs(): SliderTabComponent[] {
|
|
return this.tabs.toArray().filter(tab => tab.align === 'left');
|
|
}
|
|
|
|
get rightTabs(): SliderTabComponent[] {
|
|
return this.tabs.toArray().filter(tab => tab.align === 'right');
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.subscriptions.forEach(subscription => {
|
|
if (subscription instanceof Subscription) {
|
|
subscription.unsubscribe();
|
|
} else if (subscription instanceof Function) {
|
|
subscription();
|
|
}
|
|
});
|
|
if (this.observer) {
|
|
this.observer.disconnect();
|
|
}
|
|
}
|
|
}
|