Skip to content

Commit a23069b

Browse files
authored
feat(datepcker): Add aria-labelledby attribute #8080 (#8199)
1 parent 203bc7a commit a23069b

File tree

4 files changed

+86
-23
lines changed

4 files changed

+86
-23
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ All notable changes for each version of this project will be documented in this
44
## 10.2.0
55

66
### General
7+
- `IgxDatePicker`
8+
- 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.
79
- `IgxInputGroup`
810
- **Breaking Change** - Removed `fluent`, `fluent_search`, `bootstrap`, and `indigo` as possible values for the `type` input property.
911
- **Behavioral Change** - The styling of the input group is now dictated by the theme being used. The remaining `types` - `line`, `border`, and `box` will only have effect on the styling when used with the `material` theme. The `search` type will affect styling when used with all themes. Changing the theme at runtime will not change the styling of the input group, a page refresh is required.

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
<ng-template #readOnlyDatePickerTemplate>
66
<igx-input-group (click)="openDialog()">
7-
<label igxLabel *ngIf="!labelDirective&&!labelVisibility">Date</label>
7+
<label igxLabel *ngIf="!_labelDirectiveUserTemplate&&!labelVisibility">Date</label>
88
<ng-container ngProjectAs="[igxLabel]" *ngTemplateOutlet="labelTemplate"></ng-container>
99
<igx-prefix>
1010
<igx-icon>today</igx-icon>
1111
</igx-prefix>
12-
<label *ngIf="labelVisibility&&!labelDirective" igxLabel>{{label}}</label>
12+
<label *ngIf="labelVisibility&&!_labelDirectiveUserTemplate" igxLabel>{{label}}</label>
1313
<input
1414
class="igx-date-picker__input-date"
1515
igxInput
@@ -24,12 +24,12 @@
2424

2525
<ng-template #editableDatePickerTemplate>
2626
<igx-input-group #editableInputGroup>
27-
<label igxLabel *ngIf="!labelDirective&&!labelVisibility">Date</label>
27+
<label igxLabel *ngIf="!_labelDirectiveUserTemplate&&!labelVisibility">Date</label>
2828
<ng-container ngProjectAs="[igxLabel]" *ngTemplateOutlet="labelTemplate"></ng-container>
2929
<igx-prefix (click)="onOpenClick($event)">
3030
<igx-icon>today</igx-icon>
3131
</igx-prefix>
32-
<label *ngIf="labelVisibility&&!labelDirective" igxLabel>{{label}}</label>
32+
<label *ngIf="labelVisibility&&!_labelDirectiveUserTemplate" igxLabel>{{label}}</label>
3333
<input
3434
class="igx-date-picker__input-date"
3535
igxInput

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

Lines changed: 50 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,
@@ -314,7 +314,43 @@ describe('IgxDatePicker', () => {
314314
const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement;
315315
expect(input.tabIndex).toBe(3);
316316
});
317+
});
318+
319+
describe('ARIA Tests', () => {
320+
let labelID: string;
321+
let inputLabelledBy: string;
322+
let dom: DebugElement;
323+
324+
it('ARIA Test for a picker with an input group template', () => {
325+
const fixture = TestBed.createComponent(IgxDatePickerRetemplatedComponent);
326+
fixture.detectChanges();
327+
dom = fixture.debugElement;
328+
329+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
330+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
331+
expect(inputLabelledBy).toEqual(labelID);
332+
});
333+
334+
it('ARIA Test for picker with a dialog mode', () => {
335+
const fixture = TestBed.createComponent(IgxDatePickerTestComponent);
336+
fixture.detectChanges();
337+
dom = fixture.debugElement;
317338

339+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
340+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
341+
expect(inputLabelledBy).toEqual(labelID);
342+
});
343+
344+
345+
it('ARIA Test for picker with a dropdown mode', () => {
346+
const fixture = TestBed.createComponent(IgxDatePickerOpeningComponent);
347+
fixture.detectChanges();
348+
dom = fixture.debugElement;
349+
350+
labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
351+
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
352+
expect(inputLabelledBy).toEqual(labelID);
353+
});
318354
});
319355

320356
describe('DatePicker with passed date', () => {
@@ -1453,6 +1489,7 @@ describe('IgxDatePicker', () => {
14531489
let moduleRef;
14541490
let injector;
14551491
let inputGroup: IgxInputGroupComponent;
1492+
let renderer2: Renderer2;
14561493

14571494
beforeEach(() => {
14581495
ngModel = {
@@ -1476,10 +1513,13 @@ describe('IgxDatePicker', () => {
14761513
moduleRef = {};
14771514
injector = { get: () => ngModel };
14781515
inputGroup = new IgxInputGroupComponent(null, null, null, document);
1516+
renderer2 = jasmine.createSpyObj('Renderer2', ['setAttribute'], [{}, 'aria-labelledby', 'test-label-id-1']);
1517+
spyOn(renderer2, 'setAttribute').and.callFake(() => {
1518+
});
14791519
});
14801520

14811521
it('should initialize date picker with required correctly', () => {
1482-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1522+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14831523
datePicker['_inputGroup'] = inputGroup;
14841524
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14851525
spyOnProperty(datePicker, 'inputGroupElement').and.returnValue(null);
@@ -1493,7 +1533,7 @@ describe('IgxDatePicker', () => {
14931533
});
14941534

14951535
it('should initialize date picker with required correctly with user template input-group', () => {
1496-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1536+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
14971537
datePicker['_inputGroupUserTemplate'] = inputGroup;
14981538
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
14991539
spyOnProperty(datePicker, 'inputGroupElement').and.returnValue(null);
@@ -1507,7 +1547,7 @@ describe('IgxDatePicker', () => {
15071547
});
15081548

15091549
it('should update inputGroup isRequired correctly', () => {
1510-
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1550+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
15111551
datePicker['_inputGroup'] = inputGroup;
15121552
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
15131553
spyOnProperty(datePicker, 'inputGroupElement').and.returnValue(null);
@@ -1619,14 +1659,16 @@ export class IgxDatePickerNgModelComponent {
16191659
<igx-date-picker>
16201660
<ng-template igxDatePickerTemplate let-displayData="displayData">
16211661
<igx-input-group>
1622-
<label igxLabel>Date</label>
1662+
<label igxLabel>Custom Date Label</label>
16231663
<input igxInput [value]="displayData" required />
16241664
</igx-input-group>
16251665
</ng-template>
16261666
</igx-date-picker>
16271667
`
16281668
})
1629-
export class IgxDatePickerRetemplatedComponent { }
1669+
export class IgxDatePickerRetemplatedComponent {
1670+
@ViewChild(IgxDatePickerComponent, { static: true }) public datePicker: IgxDatePickerComponent;
1671+
}
16301672

16311673
@Component({
16321674
template: `

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

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
Injector,
2121
AfterViewChecked,
2222
ContentChildren,
23-
QueryList
23+
QueryList,
24+
Renderer2
2425
} from '@angular/core';
2526
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl, NG_VALIDATORS, ValidationErrors } from '@angular/forms';
2627
import {
@@ -32,7 +33,13 @@ import {
3233
isDateInRanges
3334
} from '../calendar/public_api';
3435
import { IgxIconModule } from '../icon/public_api';
35-
import { IgxInputGroupModule, IgxInputDirective, IgxInputGroupComponent, IgxInputState } from '../input-group/public_api';
36+
import {
37+
IgxInputGroupModule,
38+
IgxInputDirective,
39+
IgxInputGroupComponent,
40+
IgxInputState,
41+
IgxLabelDirective
42+
} from '../input-group/public_api';
3643
import { Subject, fromEvent, animationFrameScheduler, interval, Subscription } from 'rxjs';
3744
import { filter, takeUntil, throttle } from 'rxjs/operators';
3845
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
@@ -61,7 +68,6 @@ import { IgxDatePickerTemplateDirective, IgxDatePickerActionsDirective } from '.
6168
import { IgxCalendarContainerComponent } from './calendar-container.component';
6269
import { InteractionMode } from '../core/enums';
6370
import { fadeIn, fadeOut } from '../animations/fade';
64-
import { IgxLabelDirective } from '../directives/label/label.directive';
6571
import { DeprecateProperty } from '../core/deprecateDecorators';
6672

6773
let NEXT_ID = 0;
@@ -429,7 +435,10 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
429435
public element: ElementRef,
430436
private _cdr: ChangeDetectorRef,
431437
private _moduleRef: NgModuleRef<any>,
432-
private _injector: Injector) { }
438+
private _injector: Injector,
439+
private _renderer: Renderer2) {
440+
}
441+
433442

434443
/**
435444
* Gets the input group template.
@@ -655,20 +664,14 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
655664
@ViewChild('readOnlyDatePickerTemplate', { read: TemplateRef, static: true })
656665
protected readOnlyDatePickerTemplate: TemplateRef<any>;
657666

658-
/*
659-
* @hidden @internal
660-
*/
661-
@ContentChild(IgxLabelDirective)
662-
public labelDirective: IgxLabelDirective;
663-
664667
/*
665668
* @hidden
666669
*/
667670
@ViewChild('editableDatePickerTemplate', { read: TemplateRef, static: true })
668671
protected editableDatePickerTemplate: TemplateRef<any>;
669672

670673
/*
671-
* @hidden
674+
* @hidden @internal
672675
*/
673676
@ViewChild(IgxInputGroupComponent)
674677
protected _inputGroup: IgxInputGroupComponent;
@@ -688,6 +691,13 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
688691
@ContentChildren(IgxInputDirective, { descendants: true })
689692
private _inputDirectiveUserTemplates: QueryList<IgxInputDirective>;
690693

694+
@ViewChild(IgxLabelDirective)
695+
protected _labelDirective: IgxLabelDirective;
696+
697+
/** @hidden @internal */
698+
@ContentChild(IgxLabelDirective)
699+
public _labelDirectiveUserTemplate: IgxLabelDirective;
700+
691701
/**
692702
* @hidden
693703
*/
@@ -833,6 +843,11 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
833843
return this._inputDirective || this._inputDirectiveUserTemplates.first || null;
834844
}
835845

846+
/** @hidden @internal */
847+
public get labelDirective(): IgxLabelDirective {
848+
return this._labelDirective || this._labelDirectiveUserTemplate || null;
849+
}
850+
836851
/** @hidden @internal */
837852
public ngOnInit(): void {
838853
this._positionSettings = {
@@ -951,6 +966,10 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
951966
}
952967
// TODO: persist validation state when dynamically changing 'dropdown' to 'dialog' ot vice versa.
953968
// For reference -> it is currently persisted if a user template is passed (as template is not recreated)
969+
970+
if (this.labelDirective) {
971+
this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
972+
}
954973
}
955974

956975
protected onStatusChanged() {

0 commit comments

Comments
 (0)