openaire-library/sharedComponents/tabs/slider-tabs.component.ts

280 lines
10 KiB
TypeScript
Raw Normal View History

import {
AfterViewInit,
ChangeDetectorRef,
Component,
ContentChildren,
ElementRef, EventEmitter,
Input, OnDestroy, Output,
QueryList,
ViewChild
} from "@angular/core";
import {SliderTabComponent} from "./slider-tab.component";
import {ActivatedRoute, Router} from "@angular/router";
import {Subscription} from "rxjs";
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'">
<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"
(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"
(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>
<slider-arrow *ngIf="position === 'horizontal' && arrows" type="previous"></slider-arrow>
<slider-arrow *ngIf="position === 'horizontal' && arrows" type="next"></slider-arrow>
</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 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;
constructor(private route: ActivatedRoute,
private router: Router,
private cdr: ChangeDetectorRef) {
}
ngAfterViewInit() {
2023-07-19 13:09:10 +02:00
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');
}
2023-07-19 13:09:10 +02:00
});
if (this.type === 'static') {
let tabs = UIkit.tab(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);
}
2023-07-19 13:09:10 +02:00
} 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();
}
}
}