From b671b0872f8f706485bc061a20e2201371873975 Mon Sep 17 00:00:00 2001 From: Ivan Kitanov Date: Tue, 3 Jun 2025 18:19:21 +0300 Subject: [PATCH 1/4] fix(dropdown): Preventing aria-label to defualt to id --- .../src/lib/drop-down/drop-down-item.base.ts | 6 +-- .../lib/drop-down/drop-down.component.spec.ts | 52 ++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts index b18d65bf2d2..90bef866dbf 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down-item.base.ts @@ -34,11 +34,11 @@ export class IgxDropDownItemBaseDirective implements DoCheck { @HostBinding('attr.aria-label') @Input() - public get ariaLabel(): string { - return this._label ? this._label : this.value ? this.value : this.id; + public get ariaLabel(): string | null { + return this._label ? this._label : this.value ? this.value : null; } - public set ariaLabel(value: string) { + public set ariaLabel(value: string | null) { this._label = value; } diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts index 4aa4089fef9..fc2983fffb2 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts @@ -29,7 +29,7 @@ const CSS_CLASS_DISABLED = 'igx-drop-down__item--disabled'; const CSS_CLASS_HEADER = 'igx-drop-down__header'; const CSS_CLASS_TABS = '.igx-tabs__header-item'; -describe('IgxDropDown ', () => { +fdescribe('IgxDropDown ', () => { let fixture; let dropdown: IgxDropDownComponent; describe('Unit tests', () => { @@ -1034,6 +1034,56 @@ describe('IgxDropDown ', () => { }); }); describe('Rendering', () => { + describe('Accessibility', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + IgxDropDownTestComponent, + ] + }).compileComponents(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(IgxDropDownTestComponent); + fixture.detectChanges(); + dropdown = fixture.componentInstance.dropDown; + }); + it('should set the aria-label property correctly', () => { + fixture = TestBed.createComponent(IgxDropDownTestComponent); + fixture.detectChanges(); + dropdown = fixture.componentInstance.dropdown; + + // Initially aria-label should be null + dropdown.toggle(); + fixture.detectChanges(); + let items = document.querySelectorAll(`.${CSS_CLASS_ITEM}`); + items.forEach(item => { + expect(item.getAttribute('aria-label')).toBeNull(); + }); + + // Set value and check if aria-label reflects it + dropdown.toggle(); + fixture.detectChanges(); + dropdown.items.forEach((item, index) => item.value = `value ${index}`); + dropdown.toggle(); + fixture.detectChanges(); + items = document.querySelectorAll(`.${CSS_CLASS_ITEM}`); + items.forEach((item, index) => { + expect(item.getAttribute('aria-label')).toBe(`value ${index}`); + }); + + // Phase 3: Set explicit ariaLabel and verify it overrides value + dropdown.toggle(); + fixture.detectChanges(); + dropdown.items.forEach((item, index) => item.ariaLabel = `label ${index}`); + dropdown.toggle(); + fixture.detectChanges(); + items = document.querySelectorAll(`.${CSS_CLASS_ITEM}`); + items.forEach((item, index) => { + expect(item.getAttribute('aria-label')).toBe(`label ${index}`); + }); + }); + }); describe('Grouped items', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ From 7ad9adfedd7a9bf2a491002152088ac1b307322d Mon Sep 17 00:00:00 2001 From: Ivan Kitanov <69432826+IvanKitanov17@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:57:15 +0300 Subject: [PATCH 2/4] chore(drop-down): Removing leftover fdescribe --- .../src/lib/drop-down/drop-down.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts index fc2983fffb2..6e69db8f21b 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts @@ -29,7 +29,7 @@ const CSS_CLASS_DISABLED = 'igx-drop-down__item--disabled'; const CSS_CLASS_HEADER = 'igx-drop-down__header'; const CSS_CLASS_TABS = '.igx-tabs__header-item'; -fdescribe('IgxDropDown ', () => { +describe('IgxDropDown ', () => { let fixture; let dropdown: IgxDropDownComponent; describe('Unit tests', () => { From 52dc0fd357529ec9ab319fbb8bcd71d69c7f23ce Mon Sep 17 00:00:00 2001 From: Ivan Kitanov Date: Tue, 22 Jul 2025 16:01:04 +0300 Subject: [PATCH 3/4] feat(dropdown): Adding active descendant fo screen readers --- .../drop-down-navigation.directive.ts | 7 +++- .../src/lib/drop-down/drop-down.base.ts | 10 ++++++ .../lib/drop-down/drop-down.component.spec.ts | 35 ++++++++++++++++--- src/app/drop-down/drop-down.sample.html | 2 ++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down-navigation.directive.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down-navigation.directive.ts index 133e239e3a7..e6d9c5a8aa0 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down-navigation.directive.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down-navigation.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Optional, Self, Input, HostListener, Inject } from '@angular/core'; +import { Directive, Optional, Self, Input, HostListener, Inject, HostBinding } from '@angular/core'; import { IGX_DROPDOWN_BASE } from './drop-down.common'; import { IDropDownNavigationDirective } from './drop-down.common'; import { IgxDropDownBaseDirective } from './drop-down.base'; @@ -53,6 +53,11 @@ export class IgxDropDownItemNavigationDirective implements IDropDownNavigationDi this._target = target ? target : this.dropdown; } + @HostBinding('attr.aria-activedescendant') + public get activeDescendant(): string { + return this._target?.activeDescendant; + } + /** * Captures keydown events and calls the appropriate handlers on the target component */ diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts index 935c3b72f88..26733518050 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts @@ -162,6 +162,16 @@ export abstract class IgxDropDownBaseDirective implements IDropDownList, OnInit return this.element; } + /** + * @hidden @internal + * Gets the id of the focused item during dropdown navigation. + * This is used to update the `aria-activedescendant` attribute of + * the IgxDropDownNavigationDirective host element. + */ + public get activeDescendant (): string { + return this.focusedItem ? this.focusedItem.id : null; + } + /** * @hidden * @internal diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts index 6e69db8f21b..79fc67544e7 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts @@ -1046,13 +1046,9 @@ describe('IgxDropDown ', () => { beforeEach(() => { fixture = TestBed.createComponent(IgxDropDownTestComponent); fixture.detectChanges(); - dropdown = fixture.componentInstance.dropDown; + dropdown = fixture.componentInstance.dropdown; }); it('should set the aria-label property correctly', () => { - fixture = TestBed.createComponent(IgxDropDownTestComponent); - fixture.detectChanges(); - dropdown = fixture.componentInstance.dropdown; - // Initially aria-label should be null dropdown.toggle(); fixture.detectChanges(); @@ -1083,6 +1079,35 @@ describe('IgxDropDown ', () => { expect(item.getAttribute('aria-label')).toBe(`label ${index}`); }); }); + it('should update aria-activedescendant to the id of the focused item', fakeAsync(() => { + dropdown.toggle(); + tick(); + fixture.detectChanges(); + + const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`)).nativeElement; + let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement; + + expect(focusedItem).toBeTruthy(); + let focusedItemId = focusedItem.getAttribute('id'); + expect(focusedItemId).toBeTruthy(); + expect(dropdownElement.getAttribute('aria-activedescendant')).toBe(focusedItemId); + + dropdown.toggle(); + tick(); + fixture.detectChanges(); + dropdown.toggle(); + tick(); + fixture.detectChanges(); + + UIInteractions.triggerEventHandlerKeyDown('ArrowDown', fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`))); + tick(); + fixture.detectChanges(); + + focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement; + focusedItemId = focusedItem.getAttribute('id'); + + expect(dropdownElement.getAttribute('aria-activedescendant')).toBe(focusedItemId); + })); }); describe('Grouped items', () => { beforeEach(waitForAsync(() => { diff --git a/src/app/drop-down/drop-down.sample.html b/src/app/drop-down/drop-down.sample.html index 255bd3bcac6..9ee5ba3e376 100644 --- a/src/app/drop-down/drop-down.sample.html +++ b/src/app/drop-down/drop-down.sample.html @@ -16,6 +16,7 @@ > @for(item of items; track item) { @@ -161,6 +162,7 @@
Angular Dropdown With Action directive
@for(item of items; track item) { From d7f14177b7feed5381e537aec5428e7d93ff6402 Mon Sep 17 00:00:00 2001 From: Ivan Kitanov Date: Wed, 23 Jul 2025 09:07:56 +0300 Subject: [PATCH 4/4] chore(combo): Fixing aria-activedescendant test --- projects/igniteui-angular/src/lib/combo/combo.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index a3cfa04b310..991c2113def 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -1073,7 +1073,7 @@ describe('igxCombo', () => { const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)); expect(list.nativeElement.getAttribute('aria-multiselectable')).toEqual('true'); - expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(''); + expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(null); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', list); tick();