Skip to content

Commit 21f05db

Browse files
committed
feat(combo): make toggle button focusable and add aria label
1 parent d0361ce commit 21f05db

File tree

6 files changed

+66
-2
lines changed

6 files changed

+66
-2
lines changed

projects/igniteui-angular/src/lib/combo/combo.common.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,20 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
12121212
}
12131213
}
12141214

1215+
/** @hidden @internal */
1216+
public handleToggleKeyDown(eventArgs: KeyboardEvent) {
1217+
if (eventArgs.key === 'Enter' || eventArgs.key === ' ') {
1218+
eventArgs.preventDefault();
1219+
this.toggle();
1220+
}
1221+
}
1222+
1223+
/** @hidden @internal */
1224+
public getAriaLabel(): string {
1225+
return this.displayValue ? `Selected options` : 'No options selected';
1226+
}
1227+
1228+
12151229
/** @hidden @internal */
12161230
public registerOnChange(fn: any): void {
12171231
this._onChangeCallback = fn;

projects/igniteui-angular/src/lib/combo/combo.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
role="combobox" aria-haspopup="listbox"
1414
[attr.aria-expanded]="!dropdown.collapsed" [attr.aria-controls]="dropdown.listId"
1515
[attr.aria-labelledby]="ariaLabelledBy || label?.id || placeholder"
16+
[attr.aria-label]="getAriaLabel()"
1617
(blur)="onBlur()" />
1718
<ng-container ngProjectAs="igx-suffix">
1819
<ng-content select="igx-suffix"></ng-content>
@@ -28,7 +29,7 @@
2829
}
2930
</igx-suffix>
3031
}
31-
<igx-suffix class="igx-combo__toggle-button">
32+
<igx-suffix class="igx-combo__toggle-button" (keydown)="handleToggleKeyDown($event)" [tabindex]="disabled ? -1 : 0" role="button">
3233
@if (toggleIconTemplate) {
3334
<ng-container *ngTemplateOutlet="toggleIconTemplate; context: {$implicit: collapsed}"></ng-container>
3435
}

projects/igniteui-angular/src/lib/combo/combo.component.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,7 @@ describe('igxCombo', () => {
10581058
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
10591059
expect(input.nativeElement.getAttribute('aria-controls')).toEqual(combo.dropdown.listId);
10601060
expect(input.nativeElement.getAttribute('aria-labelledby')).toEqual(combo.placeholder);
1061+
expect(input.nativeElement.getAttribute('aria-label')).toEqual('No options selected');
10611062

10621063
const dropdown = fixture.debugElement.query(By.css(`div[role="listbox"]`));
10631064
expect(dropdown.nativeElement.getAttribute('aria-labelledby')).toEqual(combo.placeholder);
@@ -1079,6 +1080,10 @@ describe('igxCombo', () => {
10791080
tick();
10801081
fixture.detectChanges();
10811082
expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(combo.dropdown.focusedItem.id);
1083+
1084+
combo.select(['Illinois', 'Mississippi', 'Ohio']);
1085+
fixture.detectChanges();
1086+
expect(input.nativeElement.getAttribute('aria-label')).toEqual('Selected options');
10821087
}));
10831088
it('should render aria-expanded attribute properly', fakeAsync(() => {
10841089
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
@@ -2269,6 +2274,22 @@ describe('igxCombo', () => {
22692274
cancel: false
22702275
});
22712276
});
2277+
it('should toggle combo dropdown on Enter of the focused toggle icon', fakeAsync(() => {
2278+
spyOn(combo, 'toggle').and.callThrough();
2279+
const toggleBtn = fixture.debugElement.query(By.css(`.${CSS_CLASS_TOGGLEBUTTON}`));
2280+
2281+
UIInteractions.triggerEventHandlerKeyDown('Enter', toggleBtn);
2282+
tick();
2283+
fixture.detectChanges();
2284+
expect(combo.toggle).toHaveBeenCalledTimes(1);
2285+
expect(combo.collapsed).toEqual(false);
2286+
2287+
UIInteractions.triggerEventHandlerKeyDown('Enter', toggleBtn);
2288+
tick();
2289+
fixture.detectChanges();
2290+
expect(combo.toggle).toHaveBeenCalledTimes(2);
2291+
expect(combo.collapsed).toEqual(true);
2292+
}));
22722293
it('should not be able to select group header', () => {
22732294
spyOn(combo.selectionChanging, 'emit').and.callThrough();
22742295
combo.toggle();

projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@
265265
%igx-combo__toggle-button {
266266
color: var-get($theme, 'toggle-button-foreground-focus');
267267
background: var-get($theme, 'toggle-button-background-focus');
268+
269+
&:focus {
270+
color: color($color: 'secondary');
271+
}
268272
}
269273

270274
%igx-combo__clear-button {

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
[attr.aria-expanded]="!this.dropdown.collapsed" [attr.aria-controls]="this.dropdown.listId"
1818
[attr.aria-labelledby]="this.ariaLabelledBy || this.label?.id || this.placeholder"
1919
[attr.placeholder]="placeholder" [disabled]="disabled" [igxTextSelection]="!composing"
20+
[attr.aria-label]="getAriaLabel()"
2021
(input)="handleInputChange($event)" (click)="handleInputClick()"
2122
(keyup)="handleKeyUp($event)" (keydown)="handleKeyDown($event)" (blur)="onBlur()" (paste)="handleInputChange($event)"/>
2223

@@ -44,7 +45,8 @@
4445
</igx-suffix>
4546
}
4647

47-
<igx-suffix class="igx-combo__toggle-button" (click)="onClick($event)">
48+
<igx-suffix class="igx-combo__toggle-button" (click)="onClick($event)" (keydown)="handleToggleKeyDown($event)"
49+
[tabindex]="disabled ? -1 : 0" role="button">
4850
@if (toggleIconTemplate) {
4951
<ng-container *ngTemplateOutlet="toggleIconTemplate; context: {$implicit: collapsed}"></ng-container>
5052
}

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ describe('IgxSimpleCombo', () => {
617617
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
618618
expect(input.nativeElement.getAttribute('aria-controls')).toEqual(combo.dropdown.listId);
619619
expect(input.nativeElement.getAttribute('aria-labelledby')).toEqual(combo.placeholder);
620+
expect(input.nativeElement.getAttribute('aria-label')).toEqual('No options selected');
620621

621622
const dropdownListBox = fixture.debugElement.query(By.css(`[role='listbox']`));
622623
expect(dropdownListBox.nativeElement.getAttribute('aria-labelledby')).toEqual(combo.placeholder);
@@ -632,6 +633,10 @@ describe('IgxSimpleCombo', () => {
632633
tick();
633634
fixture.detectChanges();
634635
expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(combo.dropdown.focusedItem.id);
636+
637+
combo.select('Illinois');
638+
fixture.detectChanges();
639+
expect(input.nativeElement.getAttribute('aria-label')).toEqual('Selected options');
635640
}));
636641
it('should render aria-expanded attribute properly', fakeAsync(() => {
637642
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
@@ -1230,6 +1235,23 @@ describe('IgxSimpleCombo', () => {
12301235
expect(combo.displayValue).toEqual('Wisconsin');
12311236
});
12321237

1238+
it('should toggle combo dropdown on Enter of the focused toggle icon', fakeAsync(() => {
1239+
spyOn(combo, 'toggle').and.callThrough();
1240+
const toggleBtn = fixture.debugElement.query(By.css(`.${CSS_CLASS_TOGGLEBUTTON}`));
1241+
1242+
UIInteractions.triggerEventHandlerKeyDown('Enter', toggleBtn);
1243+
tick();
1244+
fixture.detectChanges();
1245+
expect(combo.toggle).toHaveBeenCalledTimes(1);
1246+
expect(combo.collapsed).toEqual(false);
1247+
1248+
UIInteractions.triggerEventHandlerKeyDown('Enter', toggleBtn);
1249+
tick();
1250+
fixture.detectChanges();
1251+
expect(combo.toggle).toHaveBeenCalledTimes(2);
1252+
expect(combo.collapsed).toEqual(true);
1253+
}));
1254+
12331255
it('should display the AddItem button when allowCustomValues is true and there is a partial match', fakeAsync(() => {
12341256
fixture.componentInstance.allowCustomValues = true;
12351257
fixture.detectChanges();

0 commit comments

Comments
 (0)