Skip to content

Commit 503ed7a

Browse files
authored
feat(datepcker): Add aria-labelledby attribute #8080 (#8199)
1 parent b405226 commit 503ed7a

File tree

3 files changed

+85
-11
lines changed

3 files changed

+85
-11
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes for each version of this project will be documented in this file.
44

5+
## 10.1.7
6+
7+
### General
8+
- `IgxDatePicker`
9+
- Added `aria-labelledby` property for the input field. This will ensure the users of assistive technologies will also know what component is used for, upon input focus.
10+
511
## 10.1.0
612

713
### General

projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Component, ViewChild, ElementRef, EventEmitter, QueryList } from '@angular/core';
2-
import { async, fakeAsync, TestBed, tick, flush, ComponentFixture } from '@angular/core/testing';
1+
import { Component, ViewChild, ElementRef, EventEmitter, QueryList, Renderer2, DebugElement } from '@angular/core';
2+
import { fakeAsync, TestBed, tick, flush, ComponentFixture, waitForAsync } from '@angular/core/testing';
33
import { FormsModule, FormGroup, FormBuilder, ReactiveFormsModule, Validators, NgControl } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -23,7 +23,7 @@ import {
2323

2424
describe('IgxDatePicker', () => {
2525
configureTestSuite();
26-
beforeAll(async(() => {
26+
beforeAll(waitForAsync(() => {
2727
TestBed.configureTestingModule({
2828
declarations: [
2929
IgxDatePickerTestComponent,
@@ -287,6 +287,43 @@ describe('IgxDatePicker', () => {
287287

288288
});
289289

290+
describe('ARIA Tests', () => {
291+
let labelID: string;
292+
let inputLabelledBy: string;
293+
let dom: DebugElement;
294+
295+
it('ARIA Test for a picker with an input group template', () => {
296+
const fixture = TestBed.createComponent(IgxDatePickerRetemplatedComponent);
297+
fixture.detectChanges();
298+
dom = fixture.debugElement;
299+
300+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
301+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
302+
expect(inputLabelledBy).toEqual(labelID);
303+
});
304+
305+
it('ARIA Test for picker with a dialog mode', () => {
306+
const fixture = TestBed.createComponent(IgxDatePickerTestComponent);
307+
fixture.detectChanges();
308+
dom = fixture.debugElement;
309+
310+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
311+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
312+
expect(inputLabelledBy).toEqual(labelID);
313+
});
314+
315+
316+
it('ARIA Test for picker with a dropdown mode', () => {
317+
const fixture = TestBed.createComponent(IgxDatePickerOpeningComponent);
318+
fixture.detectChanges();
319+
dom = fixture.debugElement;
320+
321+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
322+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
323+
expect(inputLabelledBy).toEqual(labelID);
324+
});
325+
});
326+
290327
describe('DatePicker with passed date', () => {
291328
// configureTestSuite();
292329
let fixture: ComponentFixture<IgxDatePickerWithPassedDateComponent>;
@@ -1423,6 +1460,7 @@ describe('IgxDatePicker', () => {
14231460
let moduleRef;
14241461
let injector;
14251462
let inputGroup: IgxInputGroupComponent;
1463+
let renderer2: Renderer2;
14261464

14271465
beforeEach(() => {
14281466
ngModel = {
@@ -1445,11 +1483,16 @@ describe('IgxDatePicker', () => {
14451483
};
14461484
moduleRef = {};
14471485
injector = { get: () => ngModel };
1486+
14481487
inputGroup = new IgxInputGroupComponent(null, null);
1488+
renderer2 = jasmine.createSpyObj('Renderer2', ['setAttribute'], [{}, 'aria-labelledby', 'test-label-id-1']);
1489+
1490+
spyOn(renderer2, 'setAttribute').and.callFake(() => {
1491+
});
14491492
});
14501493

14511494
it('should initialize date picker with required correctly', () => {
1452-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1495+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14531496
datePicker['_inputGroup'] = inputGroup;
14541497
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14551498
ngModel.control.validator = () => ({ required: true });
@@ -1462,7 +1505,7 @@ describe('IgxDatePicker', () => {
14621505
});
14631506

14641507
it('should initialize date picker with required correctly with user template input-group', () => {
1465-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1508+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14661509
datePicker['_inputGroupUserTemplate'] = inputGroup;
14671510
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14681511
ngModel.control.validator = () => ({ required: true });
@@ -1475,7 +1518,7 @@ describe('IgxDatePicker', () => {
14751518
});
14761519

14771520
it('should update inputGroup isRequired correctly', () => {
1478-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1521+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14791522
datePicker['_inputGroup'] = inputGroup;
14801523
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14811524
datePicker.ngOnInit();
@@ -1572,14 +1615,16 @@ export class IgxDatePickerNgModelComponent {
15721615
<igx-date-picker>
15731616
<ng-template igxDatePickerTemplate let-displayData="displayData">
15741617
<igx-input-group>
1575-
<label igxLabel>Date</label>
1618+
<label igxLabel>Custom Date Label</label>
15761619
<input igxInput [value]="displayData" required />
15771620
</igx-input-group>
15781621
</ng-template>
15791622
</igx-date-picker>
15801623
`
15811624
})
1582-
export class IgxDatePickerRetemplatedComponent { }
1625+
export class IgxDatePickerRetemplatedComponent {
1626+
@ViewChild(IgxDatePickerComponent, { static: true }) public datePicker: IgxDatePickerComponent;
1627+
}
15831628

15841629
@Component({
15851630
template: `

projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
Injector,
2121
AfterViewChecked,
2222
ContentChildren,
23-
QueryList
23+
QueryList, Renderer2
2424
} from '@angular/core';
2525
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl, NG_VALIDATORS, ValidationErrors } from '@angular/forms';
2626
import {
@@ -32,7 +32,13 @@ import {
3232
isDateInRanges
3333
} from '../calendar/public_api';
3434
import { IgxIconModule } from '../icon/public_api';
35-
import { IgxInputGroupModule, IgxInputDirective, IgxInputGroupComponent, IgxInputState } from '../input-group/public_api';
35+
import {
36+
IgxInputGroupModule,
37+
IgxInputDirective,
38+
IgxInputGroupComponent,
39+
IgxInputState,
40+
IgxLabelDirective
41+
} from '../input-group/public_api';
3642
import { Subject, fromEvent, animationFrameScheduler, interval, Subscription } from 'rxjs';
3743
import { filter, takeUntil, throttle } from 'rxjs/operators';
3844
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
@@ -394,7 +400,8 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
394400
public element: ElementRef,
395401
private _cdr: ChangeDetectorRef,
396402
private _moduleRef: NgModuleRef<any>,
397-
private _injector: Injector) { }
403+
private _injector: Injector,
404+
private _renderer: Renderer2) { }
398405

399406
/**
400407
* Gets the input group template.
@@ -647,6 +654,13 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
647654
@ContentChildren(IgxInputDirective, { descendants: true })
648655
private _inputDirectiveUserTemplates: QueryList<IgxInputDirective>;
649656

657+
@ViewChild(IgxLabelDirective)
658+
protected _labelDirective: IgxLabelDirective;
659+
660+
/** @hidden @internal */
661+
@ContentChild(IgxLabelDirective)
662+
public _labelDirectiveUserTemplate: IgxLabelDirective;
663+
650664
/**
651665
* @hidden
652666
*/
@@ -792,6 +806,11 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
792806
return this._inputDirective || this._inputDirectiveUserTemplates.first || null;
793807
}
794808

809+
/** @hidden @internal */
810+
public get labelDirective(): IgxLabelDirective {
811+
return this._labelDirective || this._labelDirectiveUserTemplate || null;
812+
}
813+
795814
/** @hidden @internal */
796815
public ngOnInit(): void {
797816
this._positionSettings = {
@@ -909,6 +928,10 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
909928
}
910929
// TODO: persist validation state when dynamically changing 'dropdown' to 'dialog' ot vice versa.
911930
// For reference -> it is currently persisted if a user template is passed (as template is not recreated)
931+
932+
if (this.labelDirective) {
933+
this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
934+
}
912935
}
913936

914937
protected onStatusChanged() {

0 commit comments

Comments
 (0)