From 522f0918751f191e94ba64c19e0bf4bb9c58c0ff Mon Sep 17 00:00:00 2001 From: Leslie Xiong Date: Tue, 7 Apr 2026 16:31:41 -0400 Subject: [PATCH] migrated `@Input` to `model` --- .../tabs/tab-group/tab-group.component.html | 4 +- .../src/tabs/tab-group/tab-group.component.ts | 40 +++++-------------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/libs/components/src/tabs/tab-group/tab-group.component.html b/libs/components/src/tabs/tab-group/tab-group.component.html index f4a2fcfbeb0..e44fbe7cb34 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.html +++ b/libs/components/src/tabs/tab-group/tab-group.component.html @@ -36,9 +36,9 @@ [id]="getTabLabelId(i)" [active]="tab.isActive()" [disabled]="tab.disabled()" - [attr.aria-selected]="selectedIndex === i" + [attr.aria-selected]="selectedIndex() === i" [hidden]="i > 0 && overflowTabIndex() !== undefined && i >= overflowTabIndex()!" - [attr.tabindex]="selectedIndex === i ? 0 : -1" + [attr.tabindex]="selectedIndex() === i ? 0 : -1" [class.tw-flex-1]="truncate" [class.tw-overflow-hidden]="truncate" (click)="selectTab(i)" diff --git a/libs/components/src/tabs/tab-group/tab-group.component.ts b/libs/components/src/tabs/tab-group/tab-group.component.ts index a0ad4d61fec..2ac27155f27 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.ts +++ b/libs/components/src/tabs/tab-group/tab-group.component.ts @@ -1,16 +1,15 @@ import { FocusKeyManager } from "@angular/cdk/a11y"; -import { coerceNumberProperty } from "@angular/cdk/coercion"; import { NgTemplateOutlet } from "@angular/common"; import { AfterContentChecked, AfterViewInit, Component, - Input, afterNextRender, contentChild, contentChildren, effect, input, + model, output, viewChild, viewChildren, @@ -58,7 +57,6 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { private readonly resizeObserver: ResizeObserver; private readonly _groupId: number; - private readonly _indexToSelect = signal(0); /** * Aria label for the tab list menu @@ -123,22 +121,10 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { return undefined; }); - /** The index of the active tab. */ - // TODO: Skipped for signal migration because: - // Accessor inputs cannot be migrated as they are too complex. - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() - get selectedIndex(): number | null { - return this._selectedIndex(); - } - set selectedIndex(value: number) { - this._indexToSelect.set(coerceNumberProperty(value, null)); - } - private readonly _selectedIndex = signal(null); + /** The index of the active tab. Supports two-way binding via `[(selectedIndex)]`. */ + readonly selectedIndex = model(0); - /** Output to enable support for two-way binding on `[(selectedIndex)]` */ - readonly selectedIndexChange = output(); + private readonly _selectedIndex = signal(null); /** Event emitted when the tab selection has changed. */ readonly selectedTabChange = output(); @@ -165,7 +151,7 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { }); effect(() => { - const indexToSelect = this._clampTabIndex(this._indexToSelect() ?? 0); + const indexToSelect = this._clampTabIndex(this.selectedIndex()); // If the selected tab didn't explicitly change, keep the previously // selected tab selected/active @@ -175,10 +161,10 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { for (let i = 0; i < tabs.length; i++) { if (tabs[i].isActive()) { - // Set both _indexToSelect and _selectedIndex to avoid firing a change + // Set both selectedIndex and _selectedIndex to avoid firing a change // event which could cause an infinite loop if adding a tab within the - // selectedIndexChange event - this._indexToSelect.set(i); + // selectedIndex change event + this.selectedIndex.set(i); this._selectedIndex.set(i); selectedTab = tabs[i]; break; @@ -207,7 +193,7 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { } selectTab(index: number) { - this.selectedIndex = index; + this.selectedIndex.set(index); } /** @@ -215,8 +201,8 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { * should be currently selected. */ ngAfterContentChecked(): void { - const indexToSelect = this._clampTabIndex(this._indexToSelect() ?? 0); - this._indexToSelect.set(indexToSelect); + const indexToSelect = this._clampTabIndex(this.selectedIndex()); + this.selectedIndex.set(indexToSelect); if (this._selectedIndex() != indexToSelect) { const isFirstRun = this._selectedIndex() == null; @@ -234,10 +220,6 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.resolve().then(() => { this.tabs().forEach((tab, index) => tab.isActive.set(index === indexToSelect)); - - if (!isFirstRun) { - this.selectedIndexChange.emit(indexToSelect); - } }); // Manually update the _selectedIndex and keyManager active item