Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve calendar keyboard accessibility #15451

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 87 additions & 15 deletions src/app/components/calendar/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export const CALENDAR_VALUE_ACCESSOR: any = {
[ngClass]="{ 'p-highlight': isSelected(date) && date.selectable, 'p-disabled': !date.selectable }"
(click)="onDateSelect($event, date)"
draggable="false"
[attr.data-date]="formatDateKey(formatDateMetaToDate(date))"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why to add attr.data-date ?

Copy link
Contributor Author

@PronDmytro PronDmytro May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To find the necessary cell for focus, you can refer to an example of its usage in row 2218. Another solution to this problem, albeit not ideal, is to use an ID. However, this approach may not be as effective.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot we'll discuss whether to add new attribute or not and review the PR again upcoming week's release.

(keydown)="onDateCellKeydown($event, date, i)"
pRipple
>
Expand Down Expand Up @@ -1143,6 +1144,8 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {

_defaultDate!: Date;

_focusKey: Nullable<string> = null;

private window: Window;

get locale() {
Expand Down Expand Up @@ -1622,6 +1625,14 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
return formattedValue;
}

formatDateMetaToDate(dateMeta: any): Date {
return new Date(dateMeta.year, dateMeta.month, dateMeta.day);
}

formatDateKey(date: Date): string {
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
}

setCurrentHourPM(hours: number) {
if (this.hourFormat == '12') {
this.pm = hours > 11;
Expand All @@ -1642,7 +1653,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
}

selectDate(dateMeta: any) {
let date = new Date(dateMeta.year, dateMeta.month, dateMeta.day);
let date = this.formatDateMetaToDate(dateMeta);

if (this.showTime) {
if (this.hourFormat == '12') {
Expand Down Expand Up @@ -1837,7 +1848,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
isDateBetween(start: Date, end: Date, dateMeta: any) {
let between: boolean = false;
if (ObjectUtils.isDate(start) && ObjectUtils.isDate(end)) {
let date: Date = new Date(dateMeta.year, dateMeta.month, dateMeta.day);
let date: Date = this.formatDateMetaToDate(dateMeta);
return start.getTime() <= date.getTime() && end.getTime() >= date.getTime();
}

Expand Down Expand Up @@ -2054,10 +2065,10 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
}
}

onDateCellKeydown(event: any, date: Date, groupIndex: number) {
onDateCellKeydown(event: any, dateMeta: any, groupIndex: number) {
const cellContent = event.currentTarget;
const cell = cellContent.parentElement;

const currentDate = this.formatDateMetaToDate(dateMeta);
switch (event.which) {
//down arrow
case 40: {
Expand Down Expand Up @@ -2145,7 +2156,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
//space
case 13:
case 32: {
this.onDateSelect(event, date);
this.onDateSelect(event, dateMeta);
event.preventDefault();
break;
}
Expand All @@ -2166,6 +2177,52 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
break;
}

// page up
case 33: {
cellContent.tabIndex = '-1';
const dateToFocus = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, currentDate.getDate());
const focusKey = this.formatDateKey(dateToFocus);
this.navigateToMonth(true, groupIndex, `span[data-date='${focusKey}']:not(.p-disabled):not(.p-ink)`);
event.preventDefault();
break;
}

// page down
case 34: {
cellContent.tabIndex = '-1';
const dateToFocus = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate());
const focusKey = this.formatDateKey(dateToFocus);
this.navigateToMonth(false, groupIndex, `span[data-date='${focusKey}']:not(.p-disabled):not(.p-ink)`);
event.preventDefault();
break;
}

//home
case 36:
cellContent.tabIndex = '-1';
const firstDayDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
const firstDayDateKey = this.formatDateKey(firstDayDate);
const firstDayCell = DomHandler.findSingle(cellContent.offsetParent, `span[data-date='${firstDayDateKey}']:not(.p-disabled):not(.p-ink)`);
if (firstDayCell) {
firstDayCell.tabIndex = '0';
firstDayCell.focus();
}
event.preventDefault();
break;

//end
case 35:
cellContent.tabIndex = '-1';
const lastDayDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
const lastDayDateKey = this.formatDateKey(lastDayDate);
const lastDayCell = DomHandler.findSingle(cellContent.offsetParent, `span[data-date='${lastDayDateKey}']:not(.p-disabled):not(.p-ink)`);
if (lastDayDate) {
lastDayCell.tabIndex = '0';
lastDayCell.focus();
}
event.preventDefault();
break;

default:
//no op
break;
Expand Down Expand Up @@ -2333,27 +2390,41 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
}
}

navigateToMonth(prev: any, groupIndex: number) {
navigateToMonth(prev: boolean, groupIndex: number, focusKey?: string) {
if (prev) {
if (this.numberOfMonths === 1 || groupIndex === 0) {
this.navigationState = { backward: true };
this._focusKey = focusKey;
this.navBackward(event);
} else {
let prevMonthContainer = this.contentViewChild.nativeElement.children[groupIndex - 1];
let cells = DomHandler.find(prevMonthContainer, '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
let focusCell = cells[cells.length - 1];
focusCell.tabIndex = '0';
focusCell.focus();
if (focusKey) {
const firstDayCell = DomHandler.findSingle(prevMonthContainer, focusKey);
firstDayCell.tabIndex = '0';
firstDayCell.focus();
} else {
let cells = DomHandler.find(prevMonthContainer, '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
let focusCell = cells[cells.length - 1];
focusCell.tabIndex = '0';
focusCell.focus();
}
}
} else {
if (this.numberOfMonths === 1 || groupIndex === this.numberOfMonths - 1) {
this.navigationState = { backward: false };
this._focusKey = focusKey;
this.navForward(event);
} else {
let nextMonthContainer = this.contentViewChild.nativeElement.children[groupIndex + 1];
let focusCell = DomHandler.findSingle(nextMonthContainer, '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
focusCell.tabIndex = '0';
focusCell.focus();
if (focusKey) {
const firstDayCell = DomHandler.findSingle(nextMonthContainer, focusKey);
firstDayCell.tabIndex = '0';
firstDayCell.focus();
} else {
let focusCell = DomHandler.findSingle(nextMonthContainer, '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
focusCell.tabIndex = '0';
focusCell.focus();
}
}
}
}
Expand All @@ -2376,7 +2447,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
} else if (this.currentView === 'year') {
cells = DomHandler.find(this.contentViewChild.nativeElement, '.p-yearpicker .p-yearpicker-year:not(.p-disabled)');
} else {
cells = DomHandler.find(this.contentViewChild.nativeElement, '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
cells = DomHandler.find(this.contentViewChild.nativeElement, this._focusKey || '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
}

if (cells && cells.length > 0) {
Expand All @@ -2388,7 +2459,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
} else if (this.currentView === 'year') {
cell = DomHandler.findSingle(this.contentViewChild.nativeElement, '.p-yearpicker .p-yearpicker-year:not(.p-disabled)');
} else {
cell = DomHandler.findSingle(this.contentViewChild.nativeElement, '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
cell = DomHandler.findSingle(this.contentViewChild.nativeElement, this._focusKey || '.p-datepicker-calendar td span:not(.p-disabled):not(.p-ink)');
}
}

Expand All @@ -2399,6 +2470,7 @@ export class Calendar implements OnInit, OnDestroy, ControlValueAccessor {
}

this.navigationState = null;
this._focusKey = null;
} else {
this.initFocusableCell();
}
Expand Down