Skip to content

Commit 4236e66

Browse files
tab buttons keyboard nav
1 parent 62b89d4 commit 4236e66

File tree

11 files changed

+538
-77
lines changed

11 files changed

+538
-77
lines changed

libs/components/tabs/src/lib/modules/tabs/fixtures/tabset.component.fixture.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<div [ngStyle]="{'max-width.px': tabMaxWidth}">
2-
<sky-tabset [ariaLabel]="ariaLabel" [ariaLabelledBy]="ariaLabelledBy">
2+
<sky-tabset
3+
[ariaLabel]="ariaLabel"
4+
[ariaLabelledBy]="ariaLabelledBy"
5+
tabStyle="tabs"
6+
>
37
<sky-tab [tabHeading]="tab1Heading" [active]="activeTab === 0">
48
<div class="tabset-test-content-1">{{tab1Content}}</div>
59
</sky-tab>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ElementRef, Injectable } from '@angular/core';
2+
3+
/**
4+
* @internal
5+
*/
6+
@Injectable()
7+
export class SkyTabButtonAdapterService {
8+
public focusButtonLink(buttonEl: ElementRef): void {
9+
buttonEl.nativeElement.querySelector('a').focus();
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SkyTabIndex } from './tab-index';
2+
3+
/**
4+
* @internal
5+
*/
6+
export type TabButtonViewModel = {
7+
active: boolean;
8+
ariaControls: string;
9+
buttonHref: string;
10+
buttonId: string;
11+
buttonText: string;
12+
buttonTextCount: string;
13+
closeable: boolean;
14+
disabled: boolean;
15+
tabIndex: SkyTabIndex;
16+
};

libs/components/tabs/src/lib/modules/tabs/tab-button.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
}"
77
>
88
<a
9+
#tabButtonEl
910
class="sky-btn-tab"
1011
[attr.role]="elementRole"
1112
[attr.aria-controls]="ariaControls"
@@ -19,9 +20,10 @@
1920
'sky-btn-tab-disabled': disabled,
2021
'sky-tab-btn-closeable': closeable
2122
}"
22-
[tabindex]="disabled ? '-1' : '0'"
23+
[tabindex]="active ? '0' : '-1'"
2324
(click)="onButtonClick($event)"
2425
(keydown)="onTabButtonKeyDown($event)"
26+
(focus)="onFocus()"
2527
>
2628
<span class="sky-tab-heading">
2729
{{ buttonText }}
@@ -34,6 +36,7 @@
3436
<button
3537
*ngIf="closeable"
3638
class="sky-btn-tab-close"
39+
[tabindex]="closeBtnTabIndex"
3740
type="button"
3841
[attr.aria-label]="'skyux_tab_close' | skyLibResources: buttonText"
3942
[disabled]="disabled"

libs/components/tabs/src/lib/modules/tabs/tab-button.component.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import {
2+
AfterViewInit,
23
ChangeDetectionStrategy,
4+
ChangeDetectorRef,
35
Component,
6+
ElementRef,
47
EventEmitter,
58
Input,
9+
OnDestroy,
610
Output,
711
ViewEncapsulation,
812
} from '@angular/core';
913

14+
import { Subject } from 'rxjs';
15+
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
16+
17+
import { SkyTabButtonAdapterService } from './tab-button-adapter.service';
18+
import { SkyTabIndex } from './tab-index';
1019
import { SkyTabsetStyle } from './tabset-style';
20+
import { SkyTabsetService } from './tabset.service';
1121

1222
/**
1323
* @internal
@@ -16,10 +26,11 @@ import { SkyTabsetStyle } from './tabset-style';
1626
selector: 'sky-tab-button',
1727
templateUrl: './tab-button.component.html',
1828
styleUrls: ['./tab-button.component.scss'],
29+
providers: [SkyTabButtonAdapterService],
1930
changeDetection: ChangeDetectionStrategy.OnPush,
2031
encapsulation: ViewEncapsulation.None,
2132
})
22-
export class SkyTabButtonComponent {
33+
export class SkyTabButtonComponent implements AfterViewInit, OnDestroy {
2334
@Input()
2435
public active: boolean;
2536

@@ -44,14 +55,17 @@ export class SkyTabButtonComponent {
4455
@Input()
4556
public disabled: boolean;
4657

58+
@Input()
59+
public tabIndex: SkyTabIndex;
60+
4761
@Input()
4862
public get tabStyle(): SkyTabsetStyle {
4963
return this.#_tabStyle;
5064
}
5165

5266
public set tabStyle(style: SkyTabsetStyle) {
5367
this.#_tabStyle = style;
54-
this.elementRole = style === 'tabs' ? 'tab' : 'link';
68+
this.elementRole = style === 'tabs' ? 'tab' : undefined;
5569
}
5670

5771
@Output()
@@ -60,10 +74,46 @@ export class SkyTabButtonComponent {
6074
@Output()
6175
public closeClick = new EventEmitter<void>();
6276

77+
constructor(
78+
elementRef: ElementRef,
79+
adapterService: SkyTabButtonAdapterService,
80+
changeDetectorRef: ChangeDetectorRef,
81+
tabsetService: SkyTabsetService
82+
) {
83+
this.#adapterService = adapterService;
84+
this.#changeDetectorRef = changeDetectorRef;
85+
this.#elementRef = elementRef;
86+
this.#tabsetService = tabsetService;
87+
}
88+
89+
public elementRole: string | undefined = 'tab';
90+
public closeBtnTabIndex = '-1';
6391

64-
public elementRole = 'tab';
6592
#_tabStyle: SkyTabsetStyle;
93+
#adapterService: SkyTabButtonAdapterService;
94+
#changeDetectorRef: ChangeDetectorRef;
95+
#elementRef: ElementRef;
96+
#tabsetService: SkyTabsetService;
97+
#ngUnsubscribe = new Subject<void>();
98+
99+
public ngAfterViewInit(): void {
100+
this.#tabsetService.focusedTabBtnIndex
101+
.pipe(distinctUntilChanged(), takeUntil(this.#ngUnsubscribe))
102+
.subscribe((focusedIndex) => {
103+
if (focusedIndex === this.tabIndex) {
104+
this.closeBtnTabIndex = '0';
105+
this.focusBtn();
106+
} else {
107+
this.closeBtnTabIndex = '-1';
108+
}
109+
this.#changeDetectorRef.markForCheck();
110+
});
111+
}
66112

113+
public ngOnDestroy(): void {
114+
this.#ngUnsubscribe.next();
115+
this.#ngUnsubscribe.complete();
116+
}
67117

68118
public onButtonClick(event: any): void {
69119
if (!this.disabled) {
@@ -95,4 +145,12 @@ export class SkyTabButtonComponent {
95145
event.stopPropagation();
96146
event.preventDefault();
97147
}
148+
149+
public focusBtn(): void {
150+
this.#adapterService.focusButtonLink(this.#elementRef);
151+
}
152+
153+
public onFocus(): void {
154+
this.#tabsetService.setFocusedTabBtnIndex(this.tabIndex);
155+
}
98156
}

libs/components/tabs/src/lib/modules/tabs/tabset-nav-button.component.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TestBed, async, fakeAsync, tick } from '@angular/core/testing';
1+
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
22
import { expect, expectAsync } from '@skyux-sdk/testing';
33
import { SkyLogService } from '@skyux/core';
44

@@ -64,13 +64,13 @@ describe('Tabset navigation button', () => {
6464

6565
describe('wizard style', () => {
6666
describe('without finish button', () => {
67-
it('should be accessible', async(async () => {
67+
it('should be accessible', async () => {
6868
const fixture = TestBed.createComponent(SkyWizardTestFormComponent);
6969
fixture.detectChanges();
7070
await fixture.whenStable();
7171
fixture.detectChanges();
7272
await expectAsync(fixture.nativeElement).toBeAccessible();
73-
}));
73+
});
7474

7575
describe('previous button', () => {
7676
it('should navigate to the previous tab when clicked', fakeAsync(() => {

libs/components/tabs/src/lib/modules/tabs/tabset.component.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
<div
22
class="sky-tabset"
3-
role="tablist"
3+
[attr.role]="elementRole"
44
[attr.aria-label]="ariaLabel"
55
[attr.aria-labelledby]="ariaLabelledBy"
66
[ngClass]="
77
'sky-tabset-mode-' + tabDisplayMode + ' sky-tabset-style-' + tabStyle
88
"
99
(window:resize)="onWindowResize()"
10+
(keydown.arrowright)="onRightKeyDown()"
11+
(keydown.arrowleft)="onLeftKeyDown()"
12+
(keydown.home)="onHomeKeyDown()"
13+
(keydown.end)="onEndKeyDown()"
1014
>
1115
<span class="sky-tabset-dropdown">
1216
<sky-dropdown *ngIf="tabDisplayMode === 'dropdown'" buttonType="tab">
@@ -88,6 +92,7 @@
8892
[buttonTextCount]="tabButton.buttonTextCount"
8993
[closeable]="tabButton.closeable"
9094
[disabled]="tabButton.disabled"
95+
[tabIndex]="tabButton.tabIndex"
9196
[tabStyle]="tabStyle"
9297
(buttonClick)="onTabButtonClick(tabButton)"
9398
(closeClick)="onTabCloseClick(tabButton)"

0 commit comments

Comments
 (0)