diff --git a/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.html b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.html new file mode 100644 index 0000000000..5f97828202 --- /dev/null +++ b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.html @@ -0,0 +1,37 @@ +

Static

+ + + +
+
+ +

Async

+ + +
+
+ +

Inverse attribute ordering

+ + + +
+
+ + + + diff --git a/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.less b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.less new file mode 100644 index 0000000000..80f9c2e115 --- /dev/null +++ b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.less @@ -0,0 +1,3 @@ +.margin-right { + margin-right: 24px; +} diff --git a/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.ts b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.ts new file mode 100644 index 0000000000..5d5d10c399 --- /dev/null +++ b/apps/doc/src/app/components/switcher/examples/switcher-async-example/switcher-async-example.component.ts @@ -0,0 +1,68 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { PrizmSwitcherItem } from '@prizm-ui/components'; +import { PrizmDestroyService } from '@prizm-ui/helpers'; +import { BehaviorSubject, switchMap, takeUntil, tap, timer } from 'rxjs'; + +@Component({ + selector: 'prizm-switcher-async-example', + templateUrl: './switcher-async-example.component.html', + styleUrls: ['./switcher-async-example.component.less'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [PrizmDestroyService], +}) +export class SwitcherAsyncExampleComponent { + public selectedIndex = 1; + public testIndex = this.selectedIndex; + public readonly switchersSetA: PrizmSwitcherItem[] = [ + { + title: 'Таблицы', + }, + { + title: 'Графики', + }, + { + title: 'Мнемосхемы', + disabled: true, + }, + { + title: 'Дашборды', + }, + ]; + + public readonly switchersSetB: PrizmSwitcherItem[] = [ + { + title: 'Москва', + }, + { + title: 'Санкт-Петербург', + }, + ]; + + public switchers: PrizmSwitcherItem[] = this.switchersSetA; + + public readonly switchers$ = timer(100).pipe( + switchMap(() => this.switchers$$), + tap(value => (this.switchers = value)), + takeUntil(this.destroy$) + ); + + private readonly switchers$$: BehaviorSubject = new BehaviorSubject< + PrizmSwitcherItem[] + >(this.switchersSetA); + + constructor(private readonly destroy$: PrizmDestroyService) {} + + public toggleIndex(): void { + this.selectedIndex === 1 ? (this.selectedIndex = 2) : (this.selectedIndex = 1); + } + + public toggleSwitchers(): void { + this.switchers === this.switchersSetA + ? this.switchers$$.next(this.switchersSetB) + : this.switchers$$.next(this.switchersSetA); + } + + public updateIdx(idx: number) { + this.selectedIndex = idx; + } +} diff --git a/apps/doc/src/app/components/switcher/switcher-example.component.html b/apps/doc/src/app/components/switcher/switcher-example.component.html index 2143af03b4..e647dda883 100644 --- a/apps/doc/src/app/components/switcher/switcher-example.component.html +++ b/apps/doc/src/app/components/switcher/switcher-example.component.html @@ -7,6 +7,10 @@ + + + + diff --git a/apps/doc/src/app/components/switcher/switcher-example.component.ts b/apps/doc/src/app/components/switcher/switcher-example.component.ts index 18f44943ec..cbece1f33c 100644 --- a/apps/doc/src/app/components/switcher/switcher-example.component.ts +++ b/apps/doc/src/app/components/switcher/switcher-example.component.ts @@ -39,6 +39,11 @@ export class SwitcherExampleComponent { HTML: import('./examples/switcher-basic-example/switcher-basic-example.component.html?raw'), }; + public readonly exampleAsyncSwitcher: TuiDocExample = { + TypeScript: import('./examples/switcher-async-example/switcher-async-example.component?raw'), + HTML: import('./examples/switcher-async-example/switcher-async-example.component.html?raw'), + }; + public readonly exampleInnerLSwitcher: TuiDocExample = { TypeScript: import('./examples/switcher-inner-l-example/switcher-inner-l-example.component?raw'), HTML: import('./examples/switcher-inner-l-example/switcher-inner-l-example.component.html?raw'), diff --git a/apps/doc/src/app/components/switcher/switcher-example.module.ts b/apps/doc/src/app/components/switcher/switcher-example.module.ts index 059f4f2b7c..6274b0e957 100644 --- a/apps/doc/src/app/components/switcher/switcher-example.module.ts +++ b/apps/doc/src/app/components/switcher/switcher-example.module.ts @@ -4,7 +4,7 @@ import { SwitcherExampleComponent } from './switcher-example.component'; import { prizmDocGenerateRoutes, PrizmAddonDocModule } from '@prizm-ui/doc'; import { RouterModule } from '@angular/router'; import { SwitcherBasicExampleComponent } from './examples/switcher-basic-example/switcher-basic-example.component'; -import { PrizmSwitcherModule } from '@prizm-ui/components'; +import { PrizmButtonComponent, PrizmSwitcherModule } from '@prizm-ui/components'; import { SwitcherInnerLExampleComponent } from './examples/switcher-inner-l-example/switcher-inner-l-example.component'; import { SwitcherInnerMExampleComponent } from './examples/switcher-inner-m-example/switcher-inner-m-example.component'; import { SwitcherOuterMExampleComponent } from './examples/switcher-outer-m-example/switcher-outer-m-example.component'; @@ -13,6 +13,7 @@ import { SwitcherOuterSExampleComponent } from './examples/switcher-outer-s-exam import { SwitcherWithIconExampleComponent } from './examples/switcher-with-icon-example/switcher-with-icon-example.component'; import { SwitcherOnlyIconExampleComponent } from './examples/switcher-only-icon-example/switcher-only-icon-example.component'; import { ReactiveFormsModule } from '@angular/forms'; +import { SwitcherAsyncExampleComponent } from './examples/switcher-async-example/switcher-async-example.component'; @NgModule({ declarations: [ @@ -25,6 +26,7 @@ import { ReactiveFormsModule } from '@angular/forms'; SwitcherOuterSExampleComponent, SwitcherWithIconExampleComponent, SwitcherOnlyIconExampleComponent, + SwitcherAsyncExampleComponent, ], imports: [ CommonModule, @@ -32,6 +34,7 @@ import { ReactiveFormsModule } from '@angular/forms'; RouterModule.forChild(prizmDocGenerateRoutes(SwitcherExampleComponent)), PrizmSwitcherModule, ReactiveFormsModule, + PrizmButtonComponent, ], }) export class SwitcherExampleModule {} diff --git a/libs/components/src/lib/components/switcher/switcher.component.ts b/libs/components/src/lib/components/switcher/switcher.component.ts index 8f27b30245..5274d71f88 100644 --- a/libs/components/src/lib/components/switcher/switcher.component.ts +++ b/libs/components/src/lib/components/switcher/switcher.component.ts @@ -5,6 +5,7 @@ import { EventEmitter, HostBinding, Input, + OnInit, Optional, Output, Self, @@ -13,10 +14,12 @@ import { PrizmSwitcherItem, PrizmSwitcherSize, PrizmSwitcherType } from './switc import { prizmDefaultProp } from '@prizm-ui/core'; import { PrizmAbstractTestId } from '../../abstract/interactive'; import { ControlValueAccessor, NgControl } from '@angular/forms'; -import { noop } from 'rxjs'; +import { BehaviorSubject, combineLatestWith, filter, noop, takeUntil, tap } from 'rxjs'; import { CommonModule } from '@angular/common'; import { PrizmSwitcherItemComponent } from './components/switcher-item/switcher-item.component'; import { PrizmSwitcherHintDirective } from './directives/switcher-hint.directive'; +import { INITIAL_SWITHCER_INDEX } from './swithcer.const'; +import { PrizmDestroyService } from '@prizm-ui/helpers'; @Component({ selector: 'prizm-switcher', @@ -25,12 +28,9 @@ import { PrizmSwitcherHintDirective } from './directives/switcher-hint.directive changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, PrizmSwitcherHintDirective, PrizmSwitcherItemComponent], + providers: [PrizmDestroyService], }) -export class PrizmSwitcherComponent extends PrizmAbstractTestId implements ControlValueAccessor { - onChange: (v: number) => void = noop; - onTouched: () => void = noop; - private selectedSwitcherIdx_ = 0; - +export class PrizmSwitcherComponent extends PrizmAbstractTestId implements ControlValueAccessor, OnInit { @Input() @prizmDefaultProp() public size: PrizmSwitcherSize = 'l'; @@ -39,17 +39,26 @@ export class PrizmSwitcherComponent extends PrizmAbstractTestId implements Contr @prizmDefaultProp() public type: PrizmSwitcherType = 'inner'; + private switchers$: BehaviorSubject = new BehaviorSubject([]); + @Input() @prizmDefaultProp() - public switchers: PrizmSwitcherItem[] = []; + public set switchers(value: PrizmSwitcherItem[]) { + if (value) this.switchers$.next(value); + } + get switchers(): PrizmSwitcherItem[] { + return this.switchers$.value; + } + + private selectedSwitcherIdx$: BehaviorSubject = new BehaviorSubject(INITIAL_SWITHCER_INDEX); + public selectedSwitcherIdx_ = INITIAL_SWITHCER_INDEX; @Input() @prizmDefaultProp() - public set selectedSwitcherIdx(idx: number) { - const item = this.switchers[idx]; - if (item) this.selectSwitcher(item, idx); + public set selectedSwitcherIdx(value: number) { + this.selectedSwitcherIdx$.next(value); } - get selectedSwitcherIdx() { + get selectedSwitcherIdx(): number { return this.selectedSwitcherIdx_; } @@ -62,20 +71,29 @@ export class PrizmSwitcherComponent extends PrizmAbstractTestId implements Contr override readonly testId_ = 'ui_switcher'; + onChange: (v: number) => void = noop; + onTouched: () => void = noop; + constructor( public readonly cdRef: ChangeDetectorRef, - @Optional() @Self() public readonly ngControl: NgControl + @Optional() @Self() public readonly ngControl: NgControl, + private readonly destroy$: PrizmDestroyService ) { super(); if (this.ngControl != null) { this.ngControl.valueAccessor = this; } } + + ngOnInit(): void { + this.handleSwitchersUpdate(); + } + public selectSwitcher(item: PrizmSwitcherItem, idx: number): void { if (this.ngControl?.disabled) return; if (item.disabled) return; if (this.selectedSwitcherIdx === idx) return; - this.selectedSwitcherIdxChange.emit((this.selectedSwitcherIdx_ = idx)); + this.selectedSwitcherIdxChange.emit((this.selectedSwitcherIdx = idx)); this.onChange(this.selectedSwitcherIdx); } @@ -92,4 +110,40 @@ export class PrizmSwitcherComponent extends PrizmAbstractTestId implements Contr public setDisabledState(isDisabled: boolean): void { this.cdRef.markForCheck(); } + + private handleSwitchersUpdate() { + this.switchers$ + .pipe( + filter((switchers: PrizmSwitcherItem[]) => !!switchers.length), + tap(switchers => { + if (!this.isIndexValid(this.selectedSwitcherIdx_, switchers)) { + this.selectSwitcher(switchers[INITIAL_SWITHCER_INDEX], INITIAL_SWITHCER_INDEX); + this.logIndexValidationError( + `selectedSwitcherIdx out of bound. Index has been reset to ${INITIAL_SWITHCER_INDEX}` + ); + } + }), + combineLatestWith(this.selectedSwitcherIdx$), + tap(([switchers, selectedSwitcherIdx]) => { + if (!this.isIndexValid(selectedSwitcherIdx, switchers)) { + this.logIndexValidationError('selectedSwitcherIdx out of bound'); + return; + } + + this.selectedSwitcherIdx_ = selectedSwitcherIdx; + + this.selectSwitcher(switchers[selectedSwitcherIdx], selectedSwitcherIdx); + }), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + private isIndexValid(idx: number, switchers: PrizmSwitcherItem[]): boolean { + return !!switchers[idx]; + } + + private logIndexValidationError(errorMsg: string) { + console.warn(errorMsg); + } } diff --git a/libs/components/src/lib/components/switcher/swithcer.const.ts b/libs/components/src/lib/components/switcher/swithcer.const.ts new file mode 100644 index 0000000000..aa7c41daad --- /dev/null +++ b/libs/components/src/lib/components/switcher/swithcer.const.ts @@ -0,0 +1 @@ +export const INITIAL_SWITHCER_INDEX = 0;