Skip to content

Commit 304daed

Browse files
committed
refactor(time-picker): add aria attributes #6482
1 parent f2c8cac commit 304daed

File tree

4 files changed

+131
-26
lines changed

4 files changed

+131
-26
lines changed

projects/igniteui-angular/src/lib/date-common/picker-base.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export abstract class PickerBaseDirective extends DisplayDensityBase implements
225225
this.locale = this.locale || this._localeId;
226226
}
227227

228-
public abstract select(value: Date | string): void;
228+
public abstract select(value: Date | DateRange | string): void;
229229
public abstract open(settings?: OverlaySettings): void;
230230
public abstract toggle(settings?: OverlaySettings): void;
231231
public abstract close(): void;

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<igx-input-group [suppressInputAutofocus]="this.isDropdown" (click)="!this.isDropdown && this.open()">
33
<input igxInput [igxDateTimeEditor]="this.inputFormat" type="text" [readonly]="!this.isDropdown"
44
[spinDelta]="this.itemsDelta" [disabled]="this.disabled" [displayFormat]="this.displayFormat"
5-
[placeholder]="this.placeholder" role="combobox" aria-haspopup="dialog"
5+
role="combobox" aria-haspopup="dialog"
66
[attr.aria-expanded]="!this.toggleDirective.collapsed" [attr.aria-labelledby]="this.label?.id" />
77

88
<igx-prefix *ngIf="!this.toggleComponents.length" (click)="this.toggle()">
@@ -51,7 +51,7 @@
5151
</div>
5252
</ng-template>
5353

54-
<div #toggleDirective="toggle" igxToggle class="igx-time-picker"
54+
<div #toggleDirective="toggle" igxToggle role="dialog" class="igx-time-picker"
5555
[ngClass]="{'igx-time-picker--dropdown': this.isDropdown, 'igx-time-picker--vertical': this.isVertical && !this.isDropdown}">
5656
<div *ngIf="!this.isDropdown" class="igx-time-picker__header">
5757
<h2 class="igx-time-picker__header-hour">
@@ -61,16 +61,16 @@ <h2 class="igx-time-picker__header-hour">
6161
<div class="igx-time-picker__main">
6262
<div class="igx-time-picker__body">
6363
<div *ngIf="this.showHoursList" #hourList [igxItemList]="'hourList'">
64-
<span [igxTimeItem]="hour" *ngFor="let hour of hourView">{{ hour }}</span>
64+
<span role="spinbutton" [attr.aria-label]="hour" [igxTimeItem]="hour" *ngFor="let hour of hourView">{{ hour }}</span>
6565
</div>
6666
<div *ngIf="this.showMinutesList" #minuteList [igxItemList]="'minuteList'">
67-
<span [igxTimeItem]="minute" *ngFor="let minute of minuteView">{{ minute }}</span>
67+
<span role="spinbutton" [attr.aria-label]="minute" [igxTimeItem]="minute" *ngFor="let minute of minuteView">{{ minute }}</span>
6868
</div>
6969
<div *ngIf="this.showSecondsList" #secondsList [igxItemList]="'secondsList'">
70-
<span [igxTimeItem]="seconds" *ngFor="let seconds of secondsView">{{ seconds }}</span>
70+
<span role="spinbutton" [attr.aria-label]="seconds" [igxTimeItem]="seconds" *ngFor="let seconds of secondsView">{{ seconds }}</span>
7171
</div>
7272
<div *ngIf="this.showAmPmList" #ampmList [igxItemList]="'ampmList'">
73-
<span [igxTimeItem]="ampm" *ngFor="let ampm of ampmView">{{ ampm }}</span>
73+
<span role="spinbutton" [attr.aria-label]="ampm" [igxTimeItem]="ampm" *ngFor="let ampm of ampmView">{{ ampm }}</span>
7474
</div>
7575
</div>
7676
<ng-container

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

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import { IgxToggleDirective, IgxToggleModule } from '../directives/toggle/toggle
1515
import { IBaseCancelableBrowserEventArgs, IBaseEventArgs, KEYS } from '../core/utils';
1616
import { DatePart, IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api';
1717
import { IgxItemListDirective } from './time-picker.directives';
18+
import { DateTimeUtil } from '../date-common/util/date-time.util';
19+
import { time } from 'console';
1820

1921
const CSS_CLASS_TIMEPICKER = 'igx-time-picker';
2022
const CSS_CLASS_INPUTGROUP = 'igx-input-group';
2123
const CSS_CLASS_INPUTGROUP_DISABLED = 'igx-input-group--disabled';
22-
const CSS_CLASS_INPUT = 'igx-input-group__input';
24+
const CSS_CLASS_INPUT = '.igx-input-group__input';
2325
const CSS_CLASS_DROPDOWN = '.igx-time-picker--dropdown';
2426
const CSS_CLASS_HOURLIST = '.igx-time-picker__hourList';
2527
const CSS_CLASS_MINUTELIST = '.igx-time-picker__minuteList';
@@ -41,7 +43,7 @@ describe('IgxTimePicker', () => {
4143
'registerOnTouchedCb',
4244
'registerOnValidatorChangeCb']);
4345
const mockInjector = jasmine.createSpyObj('Injector', { get: mockNgControl });
44-
const mockDateTimeEditorDirective = jasmine.createSpyObj('IgxDateTimeEditorDirective', { value: null });
46+
const mockDateTimeEditorDirective = jasmine.createSpyObj('IgxDateTimeEditorDirective', ['increment', 'decrement'], { value: null });
4547

4648
it('should open/close the dropdown with open()/close() method', () => {
4749
timePicker = new IgxTimePickerComponent(elementRef, null, null, null, mockInjector);
@@ -136,7 +138,6 @@ describe('IgxTimePicker', () => {
136138
const selectedDate = new Date(2020, 12, 12, 6, 45, 0);
137139
spyOn(timePicker.valueChange, 'emit').and.callThrough();
138140

139-
140141
timePicker.select(selectedDate);
141142
expect(timePicker.value).toEqual(selectedDate);
142143
expect(timePicker.valueChange.emit).toHaveBeenCalled();
@@ -163,6 +164,24 @@ describe('IgxTimePicker', () => {
163164
expect(timePicker.validationFailed.emit).toHaveBeenCalled();
164165
expect(timePicker.validationFailed.emit).toHaveBeenCalledWith(args);
165166
});
167+
168+
xit('should change date parts correctly with increment() and decrement() methods', () => {
169+
timePicker = new IgxTimePickerComponent(elementRef, null, null, null, null);
170+
(timePicker as any).dateTimeEditor = mockDateTimeEditorDirective;
171+
172+
const date = new Date(2020, 12, 12, 10, 30, 30);
173+
timePicker.value = new Date(date);
174+
timePicker.minValue = new Date(2020, 12, 12, 6, 0, 0);
175+
timePicker.maxValue = new Date(2020, 12, 12, 16, 0, 0);
176+
timePicker.itemsDelta = { hour: 2, minute: 20, second: 1 };
177+
spyOn(timePicker.valueChange, 'emit').and.callThrough();
178+
179+
timePicker.increment(DatePart.Hours);
180+
date.setHours(date.getHours() + timePicker.itemsDelta.hour);
181+
expect(timePicker.value).toEqual(date);
182+
expect(timePicker.valueChange.emit).toHaveBeenCalled();
183+
expect(timePicker.valueChange.emit).toHaveBeenCalledWith(date);
184+
});
166185
});
167186

168187
describe('Interaction tests', () => {
@@ -171,6 +190,8 @@ describe('IgxTimePicker', () => {
171190
let inputGroup: DebugElement;
172191
let input: DebugElement;
173192
let hourColumn: DebugElement;
193+
let minutesColumn: DebugElement;
194+
let ampmColumn: DebugElement;
174195
let toggleDirectiveElement: DebugElement;
175196
let toggleDirective: IgxToggleDirective;
176197

@@ -294,7 +315,7 @@ describe('IgxTimePicker', () => {
294315
hourColumn.triggerEventHandler('wheel', event);
295316
fixture.detectChanges();
296317
hourColumn.triggerEventHandler('wheel', event);
297-
fixture.detectChanges();hourColumn.triggerEventHandler('wheel', event);
318+
fixture.detectChanges(); hourColumn.triggerEventHandler('wheel', event);
298319
fixture.detectChanges();
299320
const selectedHour = fixture.componentInstance.date.getHours() - 3;
300321
expect((timePicker.value as Date).getHours()).toEqual(selectedHour);
@@ -397,6 +418,34 @@ describe('IgxTimePicker', () => {
397418

398419
closingSub.unsubscribe();
399420
}));
421+
422+
it('should change date parts correctly with increment() and decrement() methods', () => {
423+
const date = new Date(2020, 12, 12, 10, 30, 30);
424+
timePicker.value = new Date(date);
425+
timePicker.minValue = new Date(2020, 12, 12, 6, 0, 0);
426+
timePicker.maxValue = new Date(2020, 12, 12, 16, 0, 0);
427+
timePicker.itemsDelta = { hour: 2, minute: 20, second: 15 };
428+
fixture.detectChanges();
429+
spyOn(timePicker.valueChange, 'emit').and.callThrough();
430+
431+
timePicker.increment(DatePart.Hours);
432+
date.setHours(date.getHours() + timePicker.itemsDelta.hour);
433+
expect(timePicker.value).toEqual(date);
434+
expect(timePicker.valueChange.emit).toHaveBeenCalledTimes(1);
435+
expect(timePicker.valueChange.emit).toHaveBeenCalledWith(date);
436+
437+
timePicker.increment(DatePart.Minutes);
438+
date.setMinutes(date.getMinutes() + timePicker.itemsDelta.minute);
439+
expect(timePicker.value).toEqual(date);
440+
expect(timePicker.valueChange.emit).toHaveBeenCalledTimes(2);
441+
expect(timePicker.valueChange.emit).toHaveBeenCalledWith(date);
442+
443+
timePicker.decrement(DatePart.Seconds);
444+
date.setSeconds(date.getSeconds() - timePicker.itemsDelta.second);
445+
expect(timePicker.value).toEqual(date);
446+
expect(timePicker.valueChange.emit).toHaveBeenCalledTimes(3);
447+
expect(timePicker.valueChange.emit).toHaveBeenCalledWith(date);
448+
});
400449
});
401450

402451
describe('Renedering tests', () => {
@@ -419,10 +468,21 @@ describe('IgxTimePicker', () => {
419468
timePickerElement = fixture.debugElement.query(By.css(CSS_CLASS_TIMEPICKER)).nativeElement;
420469
inputGroup = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUTGROUP}`));
421470
hourColumn = fixture.debugElement.query(By.css(CSS_CLASS_HOURLIST));
471+
minutesColumn = fixture.debugElement.query(By.css(CSS_CLASS_MINUTELIST));
472+
ampmColumn = fixture.debugElement.query(By.css(CSS_CLASS_AMPMLIST));
422473
toggleDirectiveElement = fixture.debugElement.query(By.directive(IgxToggleDirective));
423474
toggleDirective = toggleDirectiveElement.injector.get(IgxToggleDirective) as IgxToggleDirective;
424475
}));
425476

477+
it('should initialize all input properties with their default values', () => {
478+
expect(timePicker.mode).toEqual(PickerInteractionMode.DropDown);
479+
expect(timePicker.inputFormat).toEqual(DateTimeUtil.DEFAULT_TIME_INPUT_FORMAT);
480+
expect(timePicker.itemsDelta.hour).toEqual(1);
481+
expect(timePicker.itemsDelta.minute).toEqual(1);
482+
expect(timePicker.itemsDelta.second).toEqual(1);
483+
expect(timePicker.disabled).toEqual(false);
484+
});
485+
426486
it('should be able to change the mode at runtime', fakeAsync(() => {
427487
fixture.componentInstance.timePicker.mode = PickerInteractionMode.DropDown;
428488
fixture.detectChanges();
@@ -500,13 +560,52 @@ describe('IgxTimePicker', () => {
500560

501561
expect(selectedTime).toEqual(`${hours}:${minutes} ${ampm}`);
502562
}));
563+
564+
it('should apply all aria attributes correctly', fakeAsync(() => {
565+
const input = fixture.nativeElement.querySelector(CSS_CLASS_INPUT);
566+
expect(input.getAttribute('role')).toEqual('combobox');
567+
expect(input.getAttribute('aria-haspopup')).toEqual('dialog');
568+
expect(input.getAttribute('aria-labelledby')).toEqual(timePicker.label.id);
569+
expect(input.getAttribute('aria-expanded')).toEqual('false');
570+
timePicker.open();
571+
tick();
572+
fixture.detectChanges();
573+
expect(input.getAttribute('aria-expanded')).toEqual('true');
574+
let hour = 8;
575+
let minutes = 42;
576+
let ampm = 0;
577+
hourColumn.children.forEach(el => function () {
578+
expect(el.attributes.role).toEqual('spinbutton');
579+
const hours = hour < 10 ? `0${hour}` : `${hour}`;
580+
expect(el.attributes["ariaLabel"]).toEqual(hours);
581+
hour++;
582+
});
583+
minutesColumn.children.forEach(el => function () {
584+
expect(el.attributes.role).toEqual('spinbutton');
585+
expect(el.attributes["ariaLabel"]).toEqual(`${minutes}`);
586+
minutes++;
587+
});
588+
ampmColumn.children.forEach(el => function () {
589+
expect(el.attributes.role).toEqual('spinbutton');
590+
const ampmLabel = ampm === 3 ? 'AM' : ampm === 4 ? 'PM' : null;
591+
expect(el.attributes["ariaLabel"]).toEqual(ampmLabel);
592+
ampm++;
593+
});
594+
timePicker.close();
595+
tick();[[]]
596+
fixture.detectChanges();
597+
expect(input.getAttribute('aria-expanded')).toEqual('false');
598+
}));
599+
503600
});
504601
});
505602
});
506603

507604
@Component({
508605
template: `
509-
<igx-time-picker #picker [value]="date" [mode]="mode" [minValue]="minValue" [maxValue]="maxValue"></igx-time-picker>
606+
<igx-time-picker #picker [value]="date" [mode]="mode" [minValue]="minValue" [maxValue]="maxValue">
607+
<label igxLabel>Select time</label>
608+
</igx-time-picker>
510609
`
511610
})
512611
export class IgxTimePickerTestComponent {

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

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ import {
1919
Injector,
2020
LOCALE_ID, Optional, ContentChildren, QueryList, OnChanges, SimpleChanges
2121
} from '@angular/core';
22-
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl, ValidationErrors, Validator, NG_VALIDATORS } from '@angular/forms';
22+
import {
23+
ControlValueAccessor,
24+
NG_VALUE_ACCESSOR,
25+
NgControl,
26+
AbstractControl,
27+
ValidationErrors,
28+
Validator,
29+
NG_VALIDATORS
30+
} from '@angular/forms';
2331
import { HAMMER_GESTURE_CONFIG, HammerGestureConfig } from '@angular/platform-browser';
2432
import { IgxIconModule } from '../icon/public_api';
2533
import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group/input-group.component';
@@ -50,11 +58,10 @@ import { IgxTextSelectionModule } from '../directives/text-selection/text-select
5058
import { IgxLabelDirective } from '../directives/label/label.directive';
5159
import { PickerBaseDirective } from '../date-common/picker-base.directive';
5260
import { DateTimeUtil } from '../date-common/util/date-time.util';
53-
import { DatePart, DatePartDeltas } from '../directives/date-time-editor/public_api';
61+
import { DatePart } from '../directives/date-time-editor/public_api';
5462
import { PickerHeaderOrientation } from '../date-common/types';
5563
import { IgxPickerToggleComponent } from '../date-common/picker-icons.common';
5664
import { TimeFormatPipe } from './time-picker.pipes';
57-
import { defaultCipherList } from 'constants';
5865

5966

6067
let NEXT_ID = 0;
@@ -150,7 +157,7 @@ export class IgxTimePickerComponent extends PickerBaseDirective
150157
/**
151158
* Gets/Sets the interaction mode - dialog or drop down.
152159
*
153-
* @example
160+
* @example
154161
* ```html
155162
* <igx-time-picker mode="dialog"></igx-time-picker>
156163
* ```
@@ -159,14 +166,14 @@ export class IgxTimePickerComponent extends PickerBaseDirective
159166
public mode: PickerInteractionMode = PickerInteractionMode.DropDown;
160167

161168
/**
162-
* Minimum value required for the picker to remain valid.
169+
* The minimum value the picker will accept.
163170
*
164171
* @remarks
165-
* If a `string` value is passed in, it must be in the defined input format.
172+
* If a `string` value is passed in, it must be in ISO format.
166173
*
167174
* @example
168175
* ```html
169-
* <igx-time-picker format="HH:mm" [minValue]="18:00"></igx-time-picker>
176+
* <igx-time-picker [minValue]="18:00:00"></igx-time-picker>
170177
* ```
171178
*/
172179
@Input()
@@ -180,14 +187,14 @@ export class IgxTimePickerComponent extends PickerBaseDirective
180187
}
181188

182189
/**
183-
* Maximum value required for the picker to remain valid.
190+
* The maximum value the picker will accept.
184191
*
185192
* @remarks
186-
* If a `string` value is passed in, it must be in the defined input format.
193+
* If a `string` value is passed in, it must be in ISO format.
187194
*
188195
* @example
189196
* ```html
190-
* <igx-time-picker format="HH:mm" [maxValue]="20:30"></igx-time-picker>
197+
* <igx-time-picker [maxValue]="20:30:00"></igx-time-picker>
191198
* ```
192199
*/
193200
@Input()
@@ -572,11 +579,10 @@ export class IgxTimePickerComponent extends PickerBaseDirective
572579
}
573580

574581
/**
575-
* An @Input property that gets/sets the delta by which hour and minute items would be changed <br>
576-
* when the user presses the Up/Down keys.
577-
* By default `itemsDelta` is set to `{hours: 1, minutes: 1, seconds: 1}`
582+
* Delta values used to increment or decrement each editor date part on spin actions and to display time portions in the dropdown/dialog.
583+
* By default `itemsDelta` is set to `{hour: 1, minute: 1, second: 1}`
578584
* ```html
579-
* <igx-time-picker [itemsDelta]="{hours:3, minutes:5, seconds:10}" id="time-picker"></igx-time-picker>
585+
* <igx-time-picker [itemsDelta]="{hour:3, minute:5, second:10}" id="time-picker"></igx-time-picker>
580586
* ```
581587
*/
582588
@Input()

0 commit comments

Comments
 (0)