Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(components/tabs): update tab #759 #808

Merged
merged 11 commits into from
Oct 13, 2023
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="container">
<prizm-tabs (activeTabIndexChange)="tabClick()" type="contained">
<prizm-tabs [(activeTabIndex)]="activeTabIndex" type="contained">
<prizm-tab
*ngFor="let item of tabs"
[closable]="true"
Expand All @@ -11,3 +11,5 @@
</prizm-tab>
</prizm-tabs>
</div>

<div>activeTabIndex: [{{ activeTabIndex }}]</div>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PrizmTabItem } from '@prizm-ui/components';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TabsExampleClosableComponent {
activeTabIndex = 0;
public tabs: PrizmTabItem[] = [
{
title: 'Вкладка 1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
<div class="container container__{{ size }}">
<prizm-tabs
#elementTabs
[(activeTabIndex)]="activeTabIndex"
[prizmDocHostElement]="elementTabs"
[size]="size"
[canOpen]="canOpen"
[canShowMenu]="canShowMenu"
[activeTabIndex]="activeTabIndex"
prizmDocHostElementKey="PrizmTabsComponent"
>
<prizm-tab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
ElementRef,
EventEmitter,
HostBinding,
HostListener,
Input,
OnDestroy,
OnInit,
Expand All @@ -15,9 +14,13 @@ import { PrizmTabType } from '../tabs.interface';
import { PrizmTabsService } from '../tabs.service';
import { PolymorphContent } from '../../../directives';
import { combineLatest, fromEvent, Observable, of, switchMap, timeout } from 'rxjs';
import { PrizmDestroyService, PrizmLetContextService } from '@prizm-ui/helpers';
import {
Compare,
PrizmDestroyService,
PrizmLetContextService,
} from '@prizm-ui/helpers';
import { PrizmTabContext, PrizmTabMenuContext } from '../tabs.model';
import { filter, first, map, takeUntil, tap } from 'rxjs/operators';
import { filter, first, map, startWith, takeUntil, tap } from 'rxjs/operators';
import { PrizmAbstractTestId } from '../../../abstract/interactive';

@Component({
Expand Down Expand Up @@ -45,6 +48,7 @@ export class PrizmTabComponent extends PrizmAbstractTestId implements OnInit, On
}
@Output() public closeTab = new EventEmitter<void>();

private currentDomIdx!: number;
override readonly testId_ = 'ui_tab';
readonly isActiveTab$: Observable<boolean> = combineLatest([
this.idx$,
Expand Down Expand Up @@ -72,33 +76,42 @@ export class PrizmTabComponent extends PrizmAbstractTestId implements OnInit, On
tap(tab => {
if (tab === this) this.tabsService.removeTab(tab);
}),
takeUntil(this.destroy)
timeout(25)
)
.subscribe();
}

private isFromMenuTab(): boolean {
return !!this.inMenuContextService?.context?.inMenuIdx;
return Compare.isNotNullish(this.inMenuContextService?.context?.inMenuIdx);
}

private isMainProjectedTab(): boolean {
return !this.isFromMenuTab();
}

public ngOnInit(): void {
this.tabsService.tabs$
private initUpdateIndexOnDomUpdateListener(): void {
this.tabsService.removed$$
.pipe(
switchMap(() => this.tabsService.changeParent$),
startWith(void 0),
filter(() => this.isMainProjectedTab()),
tap(() => {
const currentDomIdx = Array.from(this.el.nativeElement.parentElement?.children ?? []).indexOf(
this.el.nativeElement
);
this.tabsService.updateTab(this, currentDomIdx);
if (Compare.isNotNullish(this.currentDomIdx) && currentDomIdx !== this.currentDomIdx) {
this.tabsService.moveTab(this.currentDomIdx, currentDomIdx, this);
} else {
this.tabsService.updateTab(this, currentDomIdx);
}
this.currentDomIdx = currentDomIdx;
}),
takeUntil(this.destroy)
)
.subscribe();
}

private initClickListenerToSelectTab(): void {
fromEvent(this.el.nativeElement, 'click')
.pipe(
switchMap(() => {
Expand All @@ -110,6 +123,11 @@ export class PrizmTabComponent extends PrizmAbstractTestId implements OnInit, On
.subscribe();
}

public ngOnInit(): void {
this.initUpdateIndexOnDomUpdateListener();
this.initClickListenerToSelectTab();
}

public selectTab$(): Observable<unknown> {
return this.tab$.pipe(
first(),
Expand Down
2 changes: 2 additions & 0 deletions libs/components/src/lib/components/tabs/tabs.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ export class PrizmTabsComponent extends PrizmAbstractTestId implements OnInit, O

constructor(
private readonly cdRef: ChangeDetectorRef,
private readonly elRef: ElementRef,
private readonly destroy$: PrizmDestroyService,
private readonly tabsService: PrizmTabsService
) {
super();
}

public ngOnInit(): void {
this.tabsService.initObservingTabsParent(this.tabsContainer.nativeElement);
this.mutationObserver = new MutationObserver(() => this.mutationDetector$.next());
this.resizeObserver = new ResizeObserver(() => this.mutationDetector$.next());
this.mutationObserver.observe(this.tabsContainer.nativeElement, {
Expand Down
46 changes: 34 additions & 12 deletions libs/components/src/lib/components/tabs/tabs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, combineLatest, concat, Observable, of, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, startWith, take, takeUntil, tap } from 'rxjs/operators';
import { PrizmTabComponent } from './components/tab.component';
import { filterTruthy, PrizmDestroyService } from '@prizm-ui/helpers';
import { filterTruthy, PrizmDestroyService, prizmFromMutationObserver$ } from '@prizm-ui/helpers';
import { PrizmTabCanOpen } from './tabs.model';

@Injectable()
export class PrizmTabsService implements OnDestroy {
readonly tabs = new Map<number, PrizmTabComponent>();
readonly changes$$ = new Subject<Map<number, PrizmTabComponent>>();
readonly removed$$ = new Subject<PrizmTabComponent>();
private changeParent$_!: Observable<any>;
get changeParent$() {
return this.changeParent$_;
}
readonly closeTab$$ = new Subject<Map<number, PrizmTabComponent>>();
private readonly activeTabIdx$$ = new BehaviorSubject<number>(0);
readonly activeTabIdx$ = this.activeTabIdx$$.pipe(distinctUntilChanged());

get activeTabIdx() {
return this.activeTabIdx$$.value;
}
Expand All @@ -22,6 +28,12 @@ export class PrizmTabsService implements OnDestroy {

constructor(private readonly destroy: PrizmDestroyService) {}

public initObservingTabsParent(el: HTMLElement) {
this.changeParent$_ = prizmFromMutationObserver$(el, {
subtree: true,
childList: true,
});
}
public isActiveTab(tab: PrizmTabComponent): Observable<boolean> {
return combineLatest([this.activeTabIdx$$, this.tabs$]).pipe(
map(([activeTabIdx]) => {
Expand All @@ -36,6 +48,15 @@ export class PrizmTabsService implements OnDestroy {
return this.tabs.get(idx) as PrizmTabComponent;
}

public moveTab(idx: number, toIndex: number, tab: PrizmTabComponent): void {
if (tab !== this.getTabByIdx(idx)) return;
this.tabs.delete(idx);
this.updateTab(tab, toIndex);
if (this.activeTabIdx$$.value === idx) {
this.activeTabIdx$$.next(toIndex);
}
}

public updateTab(tab: PrizmTabComponent, idx?: number): void {
const tabIdx = typeof idx !== 'number' ? this.tabs.size : idx;
if (this.tabs.get(tabIdx) === tab) return;
Expand All @@ -46,24 +67,26 @@ export class PrizmTabsService implements OnDestroy {
public removeTab(tab: PrizmTabComponent): void {
const idx = this.findTabIdx(tab);
this.tabs.delete(idx);
const currentTabIdx = this.findTabIdx(tab);
if (currentTabIdx === -1) return;
this.correctActiveTabIdx(currentTabIdx);
this.changes$$.next(this.tabs);
this.removed$$.next(tab);
const newIdx = this.correctActiveTabIdx(idx);
if (idx !== newIdx) this.changes$$.next(this.tabs);
}

private correctActiveTabIdx(idx: number = this.activeTabIdx$$.value): void {
const isActiveTab = this.activeTabIdx$$.value === idx;
let newIdx = idx - 1;
if (isActiveTab) newIdx++;
if (!this.tabs.size) newIdx = 0;
if (isActiveTab && this.activeTabIdx$$.value !== newIdx) this.activeTabIdx$$.next(newIdx);
private correctActiveTabIdx(idx: number = this.activeTabIdx$$.value): number {
if (this.tabs.has(this.activeTabIdx$$.value)) return this.activeTabIdx$$.value;
if (!this.tabs.size) return -1;
const indexes = Array.from(this.tabs.keys()).sort();
const nextIdx = indexes.find(i => i > idx);
const newIdx = nextIdx ?? (indexes.pop() as number);
this.activeTabIdx$$.next(newIdx);
return newIdx;
}
public findTabIdx(tab: PrizmTabComponent): number {
return Array.from(this.tabs.entries()).find(([, t]) => t === tab)?.[0] ?? -1;
}
public selectTab(tab: PrizmTabComponent): void {
const idx = this.findTabIdx(tab);

if (idx === -1) {
return;
}
Expand All @@ -74,7 +97,6 @@ export class PrizmTabsService implements OnDestroy {
if (idx === this.activeTabIdx) {
return;
}

(typeof this.canOpenTab === 'function' ? this.canOpenTab(tab) : of(true))
.pipe(
take(1),
Expand Down
Loading