Skip to content

Commit ef0e2d0

Browse files
committed
fixup! fix(material/tabs): avoid not having any focusable tabs
1 parent 3656dd3 commit ef0e2d0

File tree

2 files changed

+23
-11
lines changed

2 files changed

+23
-11
lines changed

goldens/material/tabs/index.api.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,6 @@ export class MatTabLink extends InkBarItem implements AfterViewInit, OnDestroy,
414414
// (undocumented)
415415
_getRole(): string | null;
416416
// (undocumented)
417-
_getTabIndex(): number;
418-
// (undocumented)
419417
_handleFocus(): void;
420418
// (undocumented)
421419
_handleKeydown(event: KeyboardEvent): void;
@@ -438,6 +436,8 @@ export class MatTabLink extends InkBarItem implements AfterViewInit, OnDestroy,
438436
// (undocumented)
439437
tabIndex: number;
440438
// (undocumented)
439+
protected _tabIndex: i0.Signal<number>;
440+
// (undocumented)
441441
static ɵcmp: i0.ɵɵComponentDeclaration<MatTabLink, "[mat-tab-link], [matTabLink]", ["matTabLink"], { "active": { "alias": "active"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "id": { "alias": "id"; "required": false; }; }, {}, never, ["*"], true, never>;
442442
// (undocumented)
443443
static ɵfac: i0.ɵɵFactoryDeclaration<MatTabLink, never>;
@@ -458,6 +458,8 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
458458
// (undocumented)
459459
_fitInkBarToContent: BehaviorSubject<boolean>;
460460
// (undocumented)
461+
_focusedItem: i0.WritableSignal<MatPaginatedTabHeaderItem | null>;
462+
// (undocumented)
461463
_getRole(): string | null;
462464
// (undocumented)
463465
_hasFocus(link: MatTabLink): boolean;

src/material/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
ViewEncapsulation,
2323
inject,
2424
HostAttributeToken,
25+
signal,
26+
computed,
2527
} from '@angular/core';
2628
import {
2729
MAT_RIPPLE_GLOBAL_OPTIONS,
@@ -39,7 +41,7 @@ import {BehaviorSubject, Subject} from 'rxjs';
3941
import {startWith, takeUntil} from 'rxjs/operators';
4042
import {ENTER, SPACE} from '@angular/cdk/keycodes';
4143
import {MAT_TABS_CONFIG, MatTabsConfig} from '../tab-config';
42-
import {MatPaginatedTabHeader} from '../paginated-tab-header';
44+
import {MatPaginatedTabHeader, MatPaginatedTabHeaderItem} from '../paginated-tab-header';
4345
import {CdkObserveContent} from '@angular/cdk/observers';
4446
import {_CdkPrivateStyleLoader} from '@angular/cdk/private';
4547

@@ -70,6 +72,8 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private';
7072
imports: [MatRipple, CdkObserveContent],
7173
})
7274
export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit, AfterViewInit {
75+
_focusedItem = signal<MatPaginatedTabHeaderItem | null>(null);
76+
7377
/** Whether the ink bar should fit its width to the size of the tab label content. */
7478
@Input({transform: booleanAttribute})
7579
get fitInkBarToContent(): boolean {
@@ -183,6 +187,11 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
183187
.subscribe(() => this.updateActiveLink());
184188

185189
super.ngAfterContentInit();
190+
191+
// Turn the `change` stream into a signal to try and avoid "changed after checked" errors.
192+
this._keyManager!.change.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() =>
193+
this._focusedItem.set(this._keyManager?.activeItem || null),
194+
);
186195
}
187196

188197
override ngAfterViewInit() {
@@ -203,12 +212,13 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
203212
for (let i = 0; i < items.length; i++) {
204213
if (items[i].active) {
205214
this.selectedIndex = i;
206-
this._changeDetectorRef.markForCheck();
207-
208215
if (this.tabPanel) {
209216
this.tabPanel._activeTabId = items[i].id;
210217
}
211-
218+
// Updating the `selectedIndex` won't trigger the `change` event on
219+
// the key manager so we need to set the signal from here.
220+
this._focusedItem.set(items[i]);
221+
this._changeDetectorRef.markForCheck();
212222
return;
213223
}
214224
}
@@ -242,7 +252,7 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit
242252
'[attr.aria-disabled]': 'disabled',
243253
'[attr.aria-selected]': '_getAriaSelected()',
244254
'[attr.id]': 'id',
245-
'[attr.tabIndex]': '_getTabIndex()',
255+
'[attr.tabIndex]': '_tabIndex()',
246256
'[attr.role]': '_getRole()',
247257
'[class.mat-mdc-tab-disabled]': 'disabled',
248258
'[class.mdc-tab--active]': 'active',
@@ -264,6 +274,10 @@ export class MatTabLink
264274
/** Whether the tab link is active or not. */
265275
protected _isActive: boolean = false;
266276

277+
protected _tabIndex = computed(() =>
278+
this._tabNavBar._focusedItem() === this ? this.tabIndex : -1,
279+
);
280+
267281
/** Whether the link is active. */
268282
@Input({transform: booleanAttribute})
269283
get active(): boolean {
@@ -397,10 +411,6 @@ export class MatTabLink
397411
_getRole(): string | null {
398412
return this._tabNavBar.tabPanel ? 'tab' : this.elementRef.nativeElement.getAttribute('role');
399413
}
400-
401-
_getTabIndex(): number {
402-
return this._tabNavBar._hasFocus(this) ? this.tabIndex : -1;
403-
}
404414
}
405415

406416
/**

0 commit comments

Comments
 (0)