Skip to content

Commit 042e102

Browse files
authored
Merge pull request #8314 from IgniteUI/ddincheva/calendarKB
Calendar keyboard navigation improvements
2 parents 170c815 + 3d62f06 commit 042e102

14 files changed

+228
-68
lines changed

CHANGELOG.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ All notable changes for each version of this project will be documented in this
3030
- `onOpening` and `onClosing` events are emitting now arguments of `ToggleViewCancelableEventArgs` type.
3131
- `IgxSelect`
3232
- Added `aria-labelledby` property for the items list container(marked as `role="listbox"`). This will ensure the users of assistive technologies will also know what the list items container is used for, upon opening.
33-
- `IgxDatePicker`
33+
- `IgxDatePicker`
3434
- **Breaking Change** - Deprecated the `label` property.
3535
- 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.
3636
- `igxNavigationDrawer`
3737
- Added `disableAnimation` property which enables/disables the animation, when toggling the drawer. Set to `false` by default.
3838
- `igxTabs`
3939
- Added `disableAnimation` property which enables/disables the transition animation of the tabs' content. Set to `false` by default.
4040
- `IgxExpansionPanel`
41-
- `IExpansionPanelEventArgs.panel` - Deprecated. Usе `owner` property to get a reference to the panel.
41+
- `IExpansionPanelEventArgs.panel` - Deprecated. Usе `owner` property to get a reference to the panel.
42+
- `IgxCalendarComponent`, `IgxMonthsViewComponent` and `IgxYearsViewComponent`
43+
- `tabIndex` property was removed in order to improve on page navigation and to be compliant with W3 accessability recommendations; Also the date grid in the calendar is now only one tab stop, the same approach is applied and in the `IgxMonthsViewComponent` and `IgxYearsViewComponent`;
4244

4345
### New Features
4446
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`

projects/igniteui-angular/src/lib/calendar/calendar-multi-view.component.spec.ts

+48-3
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,45 @@ describe('Multi-View Calendar - ', () => {
554554
HelperTestFunctions.verifyCalendarSubHeaders(fixture, [march2020, april2020, may2020]);
555555
}));
556556

557+
it('Verify tabindex is correct when navigating with arrow keys', fakeAsync(() => {
558+
calendar.hideOutsideDays = true;
559+
fixture.detectChanges();
560+
561+
let dates = HelperTestFunctions.getMonthViewDates(fixture, 0);
562+
UIInteractions.simulateClickEvent(dates[0]);
563+
fixture.detectChanges();
564+
tick();
565+
566+
UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', dates[0]);
567+
fixture.detectChanges();
568+
tick();
569+
dates = HelperTestFunctions.getMonthViewDates(fixture, 0);
570+
expect(document.activeElement).toEqual(dates[7]);
571+
expect(dates[7].tabIndex).toBe(0);
572+
573+
UIInteractions.triggerKeyDownEvtUponElem('ArrowUp', dates[7]);
574+
fixture.detectChanges();
575+
tick();
576+
dates = HelperTestFunctions.getMonthViewDates(fixture, 0);
577+
expect(document.activeElement).toEqual(dates[0]);
578+
expect(dates[0].tabIndex).toBe(0);
579+
580+
UIInteractions.triggerKeyDownEvtUponElem('ArrowRight', dates[0]);
581+
fixture.detectChanges();
582+
tick();
583+
dates = HelperTestFunctions.getMonthViewDates(fixture, 0);
584+
expect(document.activeElement).toEqual(dates[1]);
585+
expect(dates[1].tabIndex).toBe(0);
586+
587+
UIInteractions.triggerKeyDownEvtUponElem('ArrowLeft', dates[1]);
588+
fixture.detectChanges();
589+
tick();
590+
dates = HelperTestFunctions.getMonthViewDates(fixture, 0);
591+
expect(document.activeElement).toEqual(dates[0]);
592+
expect(dates[0].tabIndex).toBe(0);
593+
}));
594+
595+
557596
it('Verify navigation with pageUp', fakeAsync(() => {
558597
let monthDates = HelperTestFunctions.getMonthViewDates(fixture, 1);
559598
UIInteractions.simulateClickEvent(monthDates[16]);
@@ -694,17 +733,19 @@ describe('Multi-View Calendar - ', () => {
694733
fixture.detectChanges();
695734
inactiveDates = HelperTestFunctions.getMonthViewInactiveDates(fixture, 0);
696735
inactiveDates.forEach(date => {
697-
expect(date.tabIndex).toEqual(0);
736+
expect(date.tabIndex).toEqual(-1);
698737
});
699738

700739
monthDates = HelperTestFunctions.getMonthViewDates(fixture, 0);
701740
for (let index = 6; index < 14; index++) {
702-
expect(monthDates[index].tabIndex).toEqual(0);
741+
expect(monthDates[index].tabIndex).toEqual(-1);
703742

704743
}
744+
745+
expect(monthDates[0].tabIndex).toEqual(0);
705746
}));
706747

707-
it('Verify navigation with Home and End keys', fakeAsync(() => {
748+
it('Verify navigation with Home and End keys and check the tabindex', fakeAsync(() => {
708749
let monthDates = HelperTestFunctions.getMonthViewDates(fixture, 1);
709750
UIInteractions.simulateClickEvent(monthDates[16]);
710751
fixture.detectChanges();
@@ -716,13 +757,17 @@ describe('Multi-View Calendar - ', () => {
716757

717758
monthDates = HelperTestFunctions.getMonthViewDates(fixture, 0);
718759
expect(document.activeElement).toEqual(monthDates[0]);
760+
expect(monthDates[0].tabIndex).toEqual(0);
761+
719762

720763
UIInteractions.triggerKeyDownEvtUponElem('End', monthDates[0], true);
721764
fixture.detectChanges();
722765
tick();
723766

724767
monthDates = HelperTestFunctions.getMonthViewDates(fixture, 2);
725768
expect(document.activeElement).toEqual(monthDates[monthDates.length - 1]);
769+
expect(monthDates[monthDates.length - 1].tabIndex).toEqual(0);
770+
726771
}));
727772

728773
it('Verify navigation with Home and End keys when there are disabled dates', fakeAsync(() => {

projects/igniteui-angular/src/lib/calendar/calendar.component.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ <h2 class="igx-calendar__header-date">
5050
[selection]="selection"
5151
[locale]="locale"
5252
[value]="value"
53+
[(activeDate)]="activeDate"
5354
[viewDate]="i | IgxGetViewDate:viewDate"
5455
[weekStart]="weekStart"
5556
[formatOptions]="formatOptions"
@@ -59,7 +60,8 @@ <h2 class="igx-calendar__header-date">
5960
[hideOutsideDays]="hideOutsideDays"
6061
[showWeekNumbers]="showWeekNumbers"
6162
(onViewChanging)="viewChanging($event)"
62-
(onDateSelection)="childClicked($event)">
63+
(onDateSelection)="childClicked($event)"
64+
(monthsViewBlur)="resetActiveDate()">
6365
</igx-days-view>
6466
</div>
6567
</div>

projects/igniteui-angular/src/lib/calendar/calendar.component.ts

+27-13
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,6 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
191191
*/
192192
public callback: (next) => void;
193193

194-
/**
195-
* The default `tabindex` attribute for the component.
196-
*
197-
* @hidden
198-
* @internal
199-
*/
200-
@HostBinding('attr.tabindex')
201-
public tabindex = 0;
202-
203194
/**
204195
* The default aria role attribute for the component.
205196
*
@@ -402,6 +393,12 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
402393
return this.selectedDates ? this.selectedDates : new Date();
403394
}
404395

396+
/**
397+
* @hidden
398+
* @internal
399+
*/
400+
public activeDate = new Date().toLocaleDateString();
401+
405402
/**
406403
* @hidden
407404
* @internal
@@ -492,10 +489,10 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
492489
*/
493490
public nextMonth(isKeydownTrigger = false) {
494491
if (isKeydownTrigger && this.animationAction === 'prev') { return; }
492+
this.isKeydownTrigger = isKeydownTrigger;
495493
this.previousViewDate = this.viewDate;
496494
this.viewDate = this.calendarModel.getNextMonth(this.viewDate);
497495
this.animationAction = ScrollMonth.NEXT;
498-
this.isKeydownTrigger = isKeydownTrigger;
499496
}
500497

501498
/**
@@ -556,7 +553,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
556553
requestAnimationFrame(() => {
557554
if (this.dacadeView) {
558555
this.dacadeView.date = args;
559-
this.dacadeView.el.nativeElement.focus();
556+
this.dacadeView.calendarDir.find(date => date.isCurrentYear).nativeElement.focus();
560557
}
561558
});
562559
}
@@ -571,7 +568,7 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
571568
requestAnimationFrame(() => {
572569
if (this.dacadeView) {
573570
this.dacadeView.date = args;
574-
this.dacadeView.el.nativeElement.focus();
571+
this.dacadeView.calendarDir.find(date => date.isCurrentYear).nativeElement.focus();
575572
}
576573
});
577574
}
@@ -732,6 +729,9 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
732729
(event.fromState === 'void' && event.toState === ScrollMonth.NONE)) {
733730
this.viewDateChanged.emit({ previousValue: this.previousViewDate, currentValue: this.viewDate });
734731
}
732+
if (!this.isKeydownTrigger) {
733+
this.resetActiveDate();
734+
}
735735

736736
if (this.monthScrollDirection !== ScrollMonth.NONE) {
737737
this.scrollMonth$.next();
@@ -768,6 +768,21 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
768768
}
769769
}
770770

771+
/**
772+
* @hidden
773+
* @internal
774+
*/
775+
public resetActiveDate() {
776+
if (!this.monthViews) { return; }
777+
let dates = [];
778+
this.monthViews.map(mv => mv.dates).forEach(days => { dates = dates.concat(days.toArray()); });
779+
const date = dates.find(day => day.selected && day.isCurrentMonth) || dates.find(day => day.isToday && day.isCurrentMonth)
780+
|| dates.find(d => d.isFocusable);
781+
if (date) {
782+
this.activeDate = date.date.date.toLocaleDateString();
783+
}
784+
}
785+
771786
/**
772787
* Keyboard navigation of the calendar
773788
* @hidden
@@ -781,7 +796,6 @@ export class IgxCalendarComponent extends IgxMonthPickerBaseDirective implements
781796
return;
782797
}
783798

784-
785799
const isPageDown = event.key === 'PageDown';
786800
const step = isPageDown ? 1 : -1;
787801
let monthView = this.daysView as IgxDaysViewComponent;

projects/igniteui-angular/src/lib/calendar/calendar.directives.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,22 @@ export class IgxCalendarYearDirective {
4848
return this.isCurrentYear;
4949
}
5050

51+
@HostBinding('attr.tabindex')
52+
public get tabIndex(): number {
53+
return this.isCurrentYear ? 0 : -1;
54+
}
55+
56+
5157
public get isCurrentYear(): boolean {
5258
return this.date.getFullYear() === this.value.getFullYear();
5359
}
5460

61+
public get nativeElement() {
62+
return this.elementRef.nativeElement;
63+
}
64+
65+
constructor(public elementRef: ElementRef) {}
66+
5567
@HostListener('click')
5668
public onClick() {
5769
this.onYearSelection.emit(this.value);
@@ -75,9 +87,6 @@ export class IgxCalendarMonthDirective {
7587
@Output()
7688
public onMonthSelection = new EventEmitter<Date>();
7789

78-
@HostBinding('attr.tabindex')
79-
public tabindex = 0;
80-
8190
@HostBinding('class.igx-calendar__month')
8291
public get defaultCSS(): boolean {
8392
return !this.isCurrentMonth;

projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,14 @@ export class IgxDayItemComponent {
155155
return this.selection !== CalendarSelection.RANGE;
156156
}
157157

158-
@HostBinding('attr.tabindex')
159-
public get tabindex(): number {
160-
return this.isDisabled || this.isHidden ? -1 : 0;
161-
}
162-
163158
private _selected = false;
164159

165160
constructor(private elementRef: ElementRef) { }
166161

167-
@HostListener('click')
168-
@HostListener('keydown.enter')
169-
public onSelect() {
162+
@HostListener('click', ['$event'])
163+
@HostListener('keydown.enter', ['$event'])
164+
public onSelect(event) {
165+
event.stopPropagation();
170166
this.onDateSelection.emit(this.date);
171167
}
172168
}

projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.html

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
[specialDates]="specialDates"
2828
[outOfRangeDates]="outOfRangeDates"
2929
[hideOutsideDays]="hideOutsideDays"
30+
[attr.tabindex]="tabIndex(day)"
31+
(focus)="activeDate = day.date.toLocaleDateString()"
3032
(onDateSelection)="selectDay($event)">
3133
{{ formattedDate(day.date) }}
3234
</igx-day-item>

0 commit comments

Comments
 (0)