Skip to content

Commit 625b3ef

Browse files
authored
feat(datepcker): Add aria attribute #8080 (#8199)
1 parent 1f61938 commit 625b3ef

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
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+
## 9.1.18
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
## 9.1.16
612

713
### General

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

Lines changed: 50 additions & 7 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, async } 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';
@@ -280,6 +280,43 @@ describe('IgxDatePicker', () => {
280280

281281
});
282282

283+
describe('ARIA Tests', () => {
284+
let labelID: string;
285+
let inputLabelledBy: string;
286+
let dom: DebugElement;
287+
288+
it('ARIA Test for a picker with an input group template', () => {
289+
const fixture = TestBed.createComponent(IgxDatePickerRetemplatedComponent);
290+
fixture.detectChanges();
291+
dom = fixture.debugElement;
292+
293+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
294+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
295+
expect(inputLabelledBy).toEqual(labelID);
296+
});
297+
298+
it('ARIA Test for picker with a dialog mode', () => {
299+
const fixture = TestBed.createComponent(IgxDatePickerTestComponent);
300+
fixture.detectChanges();
301+
dom = fixture.debugElement;
302+
303+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
304+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
305+
expect(inputLabelledBy).toEqual(labelID);
306+
});
307+
308+
309+
it('ARIA Test for picker with a dropdown mode', () => {
310+
const fixture = TestBed.createComponent(IgxDatePickerOpeningComponent);
311+
fixture.detectChanges();
312+
dom = fixture.debugElement;
313+
314+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
315+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
316+
expect(inputLabelledBy).toEqual(labelID);
317+
});
318+
});
319+
283320
describe('DatePicker with passed date', () => {
284321
// configureTestSuite();
285322
let fixture: ComponentFixture<IgxDatePickerWithPassedDateComponent>;
@@ -1416,6 +1453,7 @@ describe('IgxDatePicker', () => {
14161453
let moduleRef;
14171454
let injector;
14181455
let inputGroup: IgxInputGroupComponent;
1456+
let renderer2: Renderer2;
14191457

14201458
beforeEach(() => {
14211459
ngModel = {
@@ -1438,11 +1476,14 @@ describe('IgxDatePicker', () => {
14381476
};
14391477
moduleRef = {};
14401478
injector = { get: () => ngModel };
1479+
14411480
inputGroup = new IgxInputGroupComponent(null, null);
1481+
renderer2 = jasmine.createSpyObj('Renderer2', ['setAttribute'], [{}, 'aria-labelledby', 'test-label-id-1']);
1482+
spyOn(renderer2, 'setAttribute').and.callFake(() => {});
14421483
});
14431484

14441485
it('should initialize date picker with required correctly', () => {
1445-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1486+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14461487
datePicker['_inputGroup'] = inputGroup;
14471488
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14481489
ngModel.control.validator = () => ({ required: true });
@@ -1455,7 +1496,7 @@ describe('IgxDatePicker', () => {
14551496
});
14561497

14571498
it('should initialize date picker with required correctly with user template input-group', () => {
1458-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1499+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14591500
datePicker['_inputGroupUserTemplate'] = inputGroup;
14601501
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14611502
ngModel.control.validator = () => ({ required: true });
@@ -1468,7 +1509,7 @@ describe('IgxDatePicker', () => {
14681509
});
14691510

14701511
it('should update inputGroup isRequired correctly', () => {
1471-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1512+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14721513
datePicker['_inputGroup'] = inputGroup;
14731514
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14741515
datePicker.ngOnInit();
@@ -1565,14 +1606,16 @@ export class IgxDatePickerNgModelComponent {
15651606
<igx-date-picker>
15661607
<ng-template igxDatePickerTemplate let-displayData="displayData">
15671608
<igx-input-group>
1568-
<label igxLabel>Date</label>
1609+
<label igxLabel>Custom Date Label</label>
15691610
<input igxInput [value]="displayData" required />
15701611
</igx-input-group>
15711612
</ng-template>
15721613
</igx-date-picker>
15731614
`
15741615
})
1575-
export class IgxDatePickerRetemplatedComponent { }
1616+
export class IgxDatePickerRetemplatedComponent {
1617+
@ViewChild(IgxDatePickerComponent, { static: true }) public datePicker: IgxDatePickerComponent;
1618+
}
15761619

15771620
@Component({
15781621
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';
@@ -385,7 +391,8 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
385391
public element: ElementRef,
386392
private _cdr: ChangeDetectorRef,
387393
private _moduleRef: NgModuleRef<any>,
388-
private _injector: Injector) { }
394+
private _injector: Injector,
395+
private _renderer: Renderer2) { }
389396

390397
/**
391398
* Gets the input group template.
@@ -638,6 +645,13 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
638645
@ContentChildren(IgxInputDirective, { descendants: true })
639646
private _inputDirectiveUserTemplates: QueryList<IgxInputDirective>;
640647

648+
@ViewChild(IgxLabelDirective)
649+
protected _labelDirective: IgxLabelDirective;
650+
651+
/** @hidden @internal */
652+
@ContentChild(IgxLabelDirective)
653+
public _labelDirectiveUserTemplate: IgxLabelDirective;
654+
641655
/**
642656
* @hidden
643657
*/
@@ -783,6 +797,11 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
783797
return this._inputDirective || this._inputDirectiveUserTemplates.first || null;
784798
}
785799

800+
/** @hidden @internal */
801+
public get labelDirective(): IgxLabelDirective {
802+
return this._labelDirective || this._labelDirectiveUserTemplate || null;
803+
}
804+
786805
/** @hidden @internal */
787806
public ngOnInit(): void {
788807
this._positionSettings = {
@@ -899,6 +918,10 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
899918
}
900919
// TODO: persist validation state when dynamically changing 'dropdown' to 'dialog' ot vice versa.
901920
// For reference -> it is currently persisted if a user template is passed (as template is not recreated)
921+
922+
if (this.labelDirective) {
923+
this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
924+
}
902925
}
903926

904927
protected onStatusChanged() {

0 commit comments

Comments
 (0)