Accessibility for Calendar

pull/2798/head
Tuğçe Küçükoğlu 2022-07-19 16:04:04 +03:00
parent e25ec06753
commit 5adb1e8d78
3 changed files with 403 additions and 103 deletions

View File

@ -1,24 +1,25 @@
<template>
<span ref="container" :class="containerClass" :style="style">
<input :ref="inputRef" v-if="!inline" type="text" :class="['p-inputtext p-component', inputClass]" :style="inputStyle" @input="onInput" v-bind="$attrs"
@focus="onFocus" @blur="onBlur" @keydown="onKeyDown" :readonly="!manualInput" inputmode="none">
<CalendarButton v-if="showIcon" :icon="icon" tabindex="-1" class="p-datepicker-trigger" :disabled="$attrs.disabled" @click="onButtonClick" type="button" :aria-label="inputFieldValue"/>
<span ref="container" :id="id" :class="containerClass" :style="style">
<input :ref="inputRef" v-if="!inline" type="text" role="combobox" :id="inputId" :class="['p-inputtext p-component', inputClass]" :style="inputStyle"
aria-autocomplete="none" aria-haspopup="dialog" :aria-expanded="overlayVisible" :aria-controls="panelId" inputmode="none"
@input="onInput" @focus="onFocus" @blur="onBlur" @keydown="onKeyDown" :readonly="!manualInput" v-bind="inputProps">
<CalendarButton v-if="showIcon" :icon="icon" class="p-datepicker-trigger" :disabled="disabled" @click="onButtonClick" type="button" :aria-label="$primevue.config.locale.chooseDate" aria-haspopup="dialog" :aria-expanded="overlayVisible" :aria-controls="panelId"/>
<Portal :appendTo="appendTo" :disabled="inline">
<transition name="p-connected-overlay" @enter="onOverlayEnter($event)" @after-enter="onOverlayEnterComplete" @after-leave="onOverlayAfterLeave" @leave="onOverlayLeave">
<div :ref="overlayRef" :class="panelStyleClass" v-if="inline || overlayVisible" :role="inline ? null : 'dialog'" @click="onOverlayClick" @mouseup="onOverlayMouseUp">
<div :ref="overlayRef" :id="panelId" :class="panelStyleClass" v-if="inline || overlayVisible" :role="inline ? null : 'dialog'" :aria-modal="inline ? null : 'true'" :aria-label="$primevue.config.locale.chooseDate" @click="onOverlayClick" @mouseup="onOverlayMouseUp" v-bind="panelProps">
<template v-if="!timeOnly">
<div class="p-datepicker-group-container">
<div class="p-datepicker-group" v-for="(month,groupIndex) of months" :key="month.month + month.year">
<div class="p-datepicker-header">
<slot name="header"></slot>
<button class="p-datepicker-prev p-link" v-show="groupIndex === 0" @click="onPrevButtonClick" type="button" @keydown="onContainerButtonKeydown" v-ripple :disabled="$attrs.disabled">
<button class="p-datepicker-prev p-link" v-show="groupIndex === 0" @click="onPrevButtonClick" type="button" @keydown="onContainerButtonKeydown" v-ripple :disabled="disabled" :aria-label=" currentView === 'year' ? $primevue.config.locale.prevDecade: currentView === 'month' ? $primevue.config.locale.prevYear : $primevue.config.locale.prevMonth">
<span class="p-datepicker-prev-icon pi pi-chevron-left"></span>
</button>
<div class="p-datepicker-title">
<button type="button" @click="switchToMonthView" @keydown="onContainerButtonKeydown" v-if="currentView === 'date'" class="p-datepicker-month p-link" :disabled="switchViewButtonDisabled">
<button type="button" @click="switchToMonthView" @keydown="onContainerButtonKeydown" v-if="currentView === 'date'" class="p-datepicker-month p-link" :disabled="switchViewButtonDisabled" :aria-label="$primevue.config.locale.chooseMonth">
{{getMonthName(month.month)}}
</button>
<button type="button" @click="switchToYearView" @keydown="onContainerButtonKeydown" v-if="currentView !== 'year'" class="p-datepicker-year p-link" :disabled="switchViewButtonDisabled">
<button type="button" @click="switchToYearView" @keydown="onContainerButtonKeydown" v-if="currentView !== 'year'" class="p-datepicker-year p-link" :disabled="switchViewButtonDisabled" :aria-label="$primevue.config.locale.chooseYear">
{{getYear(month)}}
</button>
<span class="p-datepicker-decade" v-if="currentView === 'year'">
@ -28,18 +29,18 @@
</span>
</div>
<button class="p-datepicker-next p-link" v-show="numberOfMonths === 1 ? true : (groupIndex === numberOfMonths - 1)"
@click="onNextButtonClick" type="button" @keydown="onContainerButtonKeydown" v-ripple :disabled="$attrs.disabled">
@click="onNextButtonClick" type="button" @keydown="onContainerButtonKeydown" v-ripple :disabled="disabled" :aria-label=" currentView === 'year' ? $primevue.config.locale.nextDecade : currentView === 'month' ? $primevue.config.locale.nextYear : $primevue.config.locale.nextMonth">
<span class="p-datepicker-next-icon pi pi-chevron-right"></span>
</button>
</div>
<div class="p-datepicker-calendar-container" v-if="currentView ==='date'">
<table class="p-datepicker-calendar">
<table class="p-datepicker-calendar" role="grid">
<thead>
<tr>
<th scope="col" v-if="showWeek" class="p-datepicker-weekheader p-disabled">
<span>{{weekHeaderLabel}}</span>
</th>
<th scope="col" v-for="weekDay of weekDays" :key="weekDay">
<th scope="col" v-for="weekDay of weekDays" :key="weekDay" :abbr="weekDay">
<span>{{weekDay}}</span>
</th>
</tr>
@ -52,11 +53,14 @@
{{month.weekNumbers[i]}}
</span>
</td>
<td v-for="date of week" :key="date.day + '' + date.month" :class="{'p-datepicker-other-month': date.otherMonth, 'p-datepicker-today': date.today}">
<td v-for="date of week" :aria-label="date.day" :key="date.day + '' + date.month" :class="{'p-datepicker-other-month': date.otherMonth, 'p-datepicker-today': date.today}">
<span :class="{'p-highlight': isSelected(date), 'p-disabled': !date.selectable}" @click="onDateSelect($event, date)"
draggable="false" @keydown="onDateCellKeydown($event,date,groupIndex)" v-ripple>
draggable="false" @keydown="onDateCellKeydown($event,date,groupIndex)" v-ripple :aria-selected="isSelected(date)">
<slot name="date" :date="date">{{date.day}}</slot>
</span>
<div v-if="isSelected(date)" class="p-hidden-accessible" aria-live="polite">
{{date.day}}
</div>
</td>
</tr>
</tbody>
@ -68,23 +72,29 @@
<span v-for="(m,i) of monthPickerValues" :key="m" @click="onMonthSelect($event, i)" @keydown="onMonthCellKeydown($event,i)"
class="p-monthpicker-month" :class="{'p-highlight': isMonthSelected(i)}" v-ripple>
{{m}}
<div v-if="isMonthSelected(i)" class="p-hidden-accessible" aria-live="polite">
{{m}}
</div>
</span>
</div>
<div class="p-yearpicker" v-if="currentView === 'year'">
<span v-for="y of yearPickerValues" :key="y" @click="onYearSelect($event, y)" @keydown="onYearCellKeydown($event,y)"
class="p-yearpicker-year" :class="{'p-highlight': isYearSelected(y)}" v-ripple>
{{y}}
<div v-if="isYearSelected(y)" class="p-hidden-accessible" aria-live="polite">
{{y}}
</div>
</span>
</div>
</template>
<div class="p-timepicker" v-if="(showTime||timeOnly) && currentView === 'date'">
<div class="p-hour-picker">
<button class="p-link" @mousedown="onTimePickerElementMouseDown($event, 0, 1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple
<button class="p-link" :aria-label="$primevue.config.locale.nextHour" @mousedown="onTimePickerElementMouseDown($event, 0, 1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple
@mouseleave="onTimePickerElementMouseLeave()" @keydown.enter="onTimePickerElementMouseDown($event, 0, 1)" @keydown.space="onTimePickerElementMouseDown($event, 0, 1)" @keyup.enter="onTimePickerElementMouseUp($event)" @keyup.space="onTimePickerElementMouseUp($event)" type="button">
<span class="pi pi-chevron-up"></span>
</button>
<span>{{formattedCurrentHour}}</span>
<button class="p-link" @mousedown="onTimePickerElementMouseDown($event, 0, -1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple
<button class="p-link" :aria-label="$primevue.config.locale.prevHour" @mousedown="onTimePickerElementMouseDown($event, 0, -1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple
@mouseleave="onTimePickerElementMouseLeave()" @keydown.enter="onTimePickerElementMouseDown($event, 0, -1)" @keydown.space="onTimePickerElementMouseDown($event, 0, -1)" @keyup.enter="onTimePickerElementMouseUp($event)" @keyup.space="onTimePickerElementMouseUp($event)" type="button">
<span class="pi pi-chevron-down"></span>
</button>
@ -93,12 +103,12 @@
<span>{{timeSeparator}}</span>
</div>
<div class="p-minute-picker">
<button class="p-link" @mousedown="onTimePickerElementMouseDown($event, 1, 1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="$attrs.disabled"
<button class="p-link" :aria-label="$primevue.config.locale.nextMinute" @mousedown="onTimePickerElementMouseDown($event, 1, 1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="disabled"
@mouseleave="onTimePickerElementMouseLeave()" @keydown.enter="onTimePickerElementMouseDown($event, 1, 1)" @keydown.space="onTimePickerElementMouseDown($event, 1, 1)" @keyup.enter="onTimePickerElementMouseUp($event)" @keyup.space="onTimePickerElementMouseUp($event)" type="button">
<span class="pi pi-chevron-up"></span>
</button>
<span>{{formattedCurrentMinute}}</span>
<button class="p-link" @mousedown="onTimePickerElementMouseDown($event, 1, -1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="$attrs.disabled"
<button class="p-link" :aria-label="$primevue.config.locale.prevMinute" @mousedown="onTimePickerElementMouseDown($event, 1, -1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="disabled"
@mouseleave="onTimePickerElementMouseLeave()" @keydown.enter="onTimePickerElementMouseDown($event, 1, -1)" @keydown.space="onTimePickerElementMouseDown($event, 1, -1)" @keyup.enter="onTimePickerElementMouseUp($event)" @keyup.space="onTimePickerElementMouseUp($event)" type="button">
<span class="pi pi-chevron-down"></span>
</button>
@ -107,12 +117,12 @@
<span>{{timeSeparator}}</span>
</div>
<div class="p-second-picker" v-if="showSeconds">
<button class="p-link" @mousedown="onTimePickerElementMouseDown($event, 2, 1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="$attrs.disabled"
<button class="p-link" :aria-label="$primevue.config.locale.nextSecond" @mousedown="onTimePickerElementMouseDown($event, 2, 1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="disabled"
@mouseleave="onTimePickerElementMouseLeave()" @keydown.enter="onTimePickerElementMouseDown($event, 2, 1)" @keydown.space="onTimePickerElementMouseDown($event, 2, 1)" @keyup.enter="onTimePickerElementMouseUp($event)" @keyup.space="onTimePickerElementMouseUp($event)" type="button">
<span class="pi pi-chevron-up"></span>
</button>
<span>{{formattedCurrentSecond}}</span>
<button class="p-link" @mousedown="onTimePickerElementMouseDown($event, 2, -1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="$attrs.disabled"
<button class="p-link" :aria-label="$primevue.config.locale.prevSecond" @mousedown="onTimePickerElementMouseDown($event, 2, -1)" @mouseup="onTimePickerElementMouseUp($event)" @keydown="onContainerButtonKeydown" v-ripple :disabled="disabled"
@mouseleave="onTimePickerElementMouseLeave()" @keydown.enter="onTimePickerElementMouseDown($event, 2, -1)" @keydown.space="onTimePickerElementMouseDown($event, 2, -1)" @keyup.enter="onTimePickerElementMouseUp($event)" @keyup.space="onTimePickerElementMouseUp($event)" type="button">
<span class="pi pi-chevron-down"></span>
</button>
@ -121,11 +131,11 @@
<span>{{timeSeparator}}</span>
</div>
<div class="p-ampm-picker" v-if="hourFormat=='12'">
<button class="p-link" @click="toggleAMPM($event)" type="button" v-ripple :disabled="$attrs.disabled">
<button class="p-link" :aria-label="$primevue.config.locale.am" @click="toggleAMPM($event)" type="button" v-ripple :disabled="disabled">
<span class="pi pi-chevron-up"></span>
</button>
<span>{{pm ? 'PM' : 'AM'}}</span>
<button class="p-link" @click="toggleAMPM($event)" type="button" v-ripple :disabled="$attrs.disabled">
<button class="p-link" :aria-label="$primevue.config.locale.pm" @click="toggleAMPM($event)" type="button" v-ripple :disabled="disabled">
<span class="pi pi-chevron-down"></span>
</button>
</div>
@ -150,7 +160,6 @@ import Portal from 'primevue/portal';
export default {
name: 'Calendar',
inheritAttrs: false,
emits: ['show', 'hide', 'input', 'month-change', 'year-change', 'date-select', 'update:modelValue', 'today-click', 'clear-click', 'focus', 'blur', 'keydown'],
props: {
modelValue: null,
@ -303,10 +312,22 @@ export default {
type: String,
default: 'body'
},
id: null,
inputId: null,
inputClass: null,
inputStyle: null,
class: null,
style: null
style: null,
inputProps: null,
panelProps: null,
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
}
},
navigationState: null,
timePickerChange: false,
@ -329,7 +350,7 @@ export default {
if (this.inline) {
this.overlay && this.overlay.setAttribute(this.attributeSelector, '');
if (!this.$attrs.disabled) {
if (!this.disabled) {
this.preventFocus = true;
this.initFocusableCell();
@ -660,15 +681,20 @@ export default {
this.decrementDecade();
}
else {
if (this.currentMonth === 0) {
this.currentMonth = 11;
if (event.shiftKey) {
this.decrementYear();
}
else {
this.currentMonth--;
}
if (this.currentMonth === 0) {
this.currentMonth = 11;
this.decrementYear();
}
else {
this.currentMonth--;
}
this.$emit('month-change', {month: this.currentMonth + 1, year: this.currentYear});
this.$emit('month-change', {month: this.currentMonth + 1, year: this.currentYear});
}
}
},
navForward(event) {
@ -685,15 +711,20 @@ export default {
this.incrementDecade();
}
else {
if (this.currentMonth === 11) {
this.currentMonth = 0;
if (event.shiftKey) {
this.incrementYear();
}
else {
this.currentMonth++;
}
if (this.currentMonth === 11) {
this.currentMonth = 0;
this.incrementYear();
}
else {
this.currentMonth++;
}
this.$emit('month-change', {month: this.currentMonth + 1, year: this.currentYear});
this.$emit('month-change', {month: this.currentMonth + 1, year: this.currentYear});
}
}
},
decrementYear() {
@ -719,7 +750,7 @@ export default {
event.preventDefault();
},
isEnabled() {
return !this.$attrs.disabled && !this.$attrs.readonly;
return !this.disabled && !this.readonly;
},
updateCurrentTimeMeta(date) {
let currentHour = date.getHours();
@ -853,7 +884,7 @@ export default {
this.$emit('year-change', {month: this.currentMonth + 1, year: this.currentYear});
},
onDateSelect(event, dateMeta) {
if (this.$attrs.disabled || !dateMeta.selectable) {
if (this.disabled || !dateMeta.selectable) {
return;
}
@ -1736,9 +1767,8 @@ export default {
const cellContent = event.currentTarget;
const cell = cellContent.parentElement;
switch (event.which) {
//down arrow
case 40: {
switch (event.code) {
case 'ArrowDown': {
cellContent.tabIndex = '-1';
let cellIndex = DomHandler.index(cell);
let nextRow = cell.parentElement.nextElementSibling;
@ -1761,8 +1791,7 @@ export default {
break;
}
//up arrow
case 38: {
case 'ArrowUp': {
cellContent.tabIndex = '-1';
let cellIndex = DomHandler.index(cell);
let prevRow = cell.parentElement.previousElementSibling;
@ -1785,14 +1814,13 @@ export default {
break;
}
//left arrow
case 37: {
case 'ArrowLeft': {
cellContent.tabIndex = '-1';
let prevCell = cell.previousElementSibling;
if (prevCell) {
let focusCell = prevCell.children[0];
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
this.navigateToMonth(true, groupIndex);
this.navigateToMonth(event, true, groupIndex);
}
else {
focusCell.tabIndex = '0';
@ -1800,20 +1828,19 @@ export default {
}
}
else {
this.navigateToMonth(true, groupIndex);
this.navigateToMonth(event, true, groupIndex);
}
event.preventDefault();
break;
}
//right arrow
case 39: {
case 'ArrowRight': {
cellContent.tabIndex = '-1';
let nextCell = cell.nextElementSibling;
if (nextCell) {
let focusCell = nextCell.children[0];
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
this.navigateToMonth(false, groupIndex);
this.navigateToMonth(event, false, groupIndex);
}
else {
focusCell.tabIndex = '0';
@ -1821,42 +1848,94 @@ export default {
}
}
else {
this.navigateToMonth(false, groupIndex);
this.navigateToMonth(event, false, groupIndex);
}
event.preventDefault();
break;
}
//enter
//space
case 13:
case 32: {
case 'Enter':
case 'Space': {
this.onDateSelect(event, date);
event.preventDefault();
break;
}
//escape
case 27: {
case 'Escape': {
this.overlayVisible = false;
event.preventDefault();
break;
}
//tab
case 9: {
case 'Tab': {
if (!this.inline) {
this.trapFocus(event);
}
break;
}
case 'Home': {
cellContent.tabIndex = '-1';
let currentRow = cell.parentElement;
let focusCell = currentRow.children[0].children[0];
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
this.navigateToMonth(event, true, groupIndex);
}
else {
focusCell.tabIndex = '0';
focusCell.focus();
}
event.preventDefault();
break;
}
case 'End': {
cellContent.tabIndex = '-1';
let currentRow = cell.parentElement;
let focusCell = currentRow.children[currentRow.children.length -1].children[0];
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
this.navigateToMonth(event, false, groupIndex);
}
else {
focusCell.tabIndex = '0';
focusCell.focus();
}
event.preventDefault();
break;
}
case 'PageUp': {
cellContent.tabIndex = '-1';
if (event.shiftKey) {
this.navigationState = {backward: true};
this.navBackward(event);
}
else this.navigateToMonth(event, true, groupIndex);
event.preventDefault();
break;
}
case 'PageDown': {
cellContent.tabIndex = '-1';
if (event.shiftKey) {
this.navigationState = {backward: false};
this.navForward(event);
}
else this.navigateToMonth(event, false, groupIndex);
event.preventDefault();
break;
}
default:
//no op
break;
}
},
navigateToMonth(prev, groupIndex) {
navigateToMonth(event, prev, groupIndex) {
if (prev) {
if (this.numberOfMonths === 1 || (groupIndex === 0)) {
this.navigationState = {backward: true};
@ -1886,14 +1965,13 @@ export default {
onMonthCellKeydown(event, index) {
const cell = event.currentTarget;
switch (event.which) {
//arrows
case 38:
case 40: {
switch (event.code) {
case 'ArrowUp':
case 'ArrowDown': {
cell.tabIndex = '-1';
var cells = cell.parentElement.children;
var cellIndex = DomHandler.index(cell);
let nextCell = cells[event.which === 40 ? cellIndex + 3 : cellIndex -3];
let nextCell = cells[event.code === 'ArrowDown' ? cellIndex + 3 : cellIndex -3];
if (nextCell) {
nextCell.tabIndex = '0';
nextCell.focus();
@ -1902,8 +1980,7 @@ export default {
break;
}
//left arrow
case 37: {
case 'ArrowLeft': {
cell.tabIndex = '-1';
let prevCell = cell.previousElementSibling;
if (prevCell) {
@ -1918,8 +1995,7 @@ export default {
break;
}
//right arrow
case 39: {
case 'ArrowRight': {
cell.tabIndex = '-1';
let nextCell = cell.nextElementSibling;
if (nextCell) {
@ -1934,24 +2010,36 @@ export default {
break;
}
//enter
//space
case 13:
case 32: {
case 'PageUp': {
if (event.shiftKey) return;
this.navigationState = {backward: true};
this.navBackward(event);
break;
}
case 'PageDown': {
if (event.shiftKey) return;
this.navigationState = {backward: false};
this.navForward(event);
break;
}
case 'Enter':
case 'Space': {
this.onMonthSelect(event, index);
event.preventDefault();
break;
}
//escape
case 27: {
case 'Escape': {
this.overlayVisible = false;
event.preventDefault();
break;
}
//tab
case 9: {
case 'Tab': {
this.trapFocus(event);
break;
}
@ -1964,14 +2052,13 @@ export default {
onYearCellKeydown(event, index) {
const cell = event.currentTarget;
switch (event.which) {
//arrows
case 38:
case 40: {
switch (event.code) {
case 'ArrowUp':
case 'ArrowDown': {
cell.tabIndex = '-1';
var cells = cell.parentElement.children;
var cellIndex = DomHandler.index(cell);
let nextCell = cells[event.which === 40 ? cellIndex + 2 : cellIndex - 2];
let nextCell = cells[event.code === 'ArrowDown' ? cellIndex + 2 : cellIndex - 2];
if (nextCell) {
nextCell.tabIndex = '0';
nextCell.focus();
@ -1980,8 +2067,7 @@ export default {
break;
}
//left arrow
case 37: {
case 'ArrowLeft': {
cell.tabIndex = '-1';
let prevCell = cell.previousElementSibling;
if (prevCell) {
@ -1996,8 +2082,7 @@ export default {
break;
}
//right arrow
case 39: {
case 'ArrowRight': {
cell.tabIndex = '-1';
let nextCell = cell.nextElementSibling;
if (nextCell) {
@ -2012,24 +2097,36 @@ export default {
break;
}
//enter
//space
case 13:
case 32: {
case 'PageUp': {
if (event.shiftKey) return;
this.navigationState = {backward: true};
this.navBackward(event);
break;
}
case 'PageDown': {
if (event.shiftKey) return;
this.navigationState = {backward: false};
this.navForward(event);
break;
}
case 'Enter':
case 'Space': {
this.onYearSelect(event, index);
event.preventDefault();
break;
}
//escape
case 27: {
case 'Escape': {
this.overlayVisible = false;
event.preventDefault();
break;
}
//tab
case 9: {
case 'Tab': {
this.trapFocus(event);
break;
}
@ -2156,14 +2253,12 @@ export default {
}
},
onContainerButtonKeydown(event) {
switch (event.which) {
//tab
case 9:
switch (event.code) {
case 'Tab':
this.trapFocus(event);
break;
//escape
case 27:
case 'Escape':
this.overlayVisible = false;
event.preventDefault();
break;
@ -2206,16 +2301,16 @@ export default {
event.target.value = this.formatValue(this.modelValue);
},
onKeyDown(event) {
if (event.keyCode === 40 && this.overlay) {
if (event.code === 'ArrowDown' && this.overlay) {
this.trapFocus(event);
}
else if (event.keyCode === 27) {
else if (event.code === 'Escape') {
if (this.overlayVisible) {
this.overlayVisible = false;
event.preventDefault();
}
}
else if (event.keyCode === 9) {
else if (event.code === 'Tab') {
if (this.overlay) {
DomHandler.getFocusableElements(this.overlay).forEach(el => el.tabIndex = '-1');
}
@ -2334,7 +2429,7 @@ export default {
{
'p-calendar-w-btn': this.showIcon,
'p-calendar-timeonly': this.timeOnly,
'p-calendar-disabled': this.$attrs.disabled,
'p-calendar-disabled': this.disabled,
'p-inputwrapper-filled': this.modelValue,
'p-inputwrapper-focus': this.focused
}
@ -2343,7 +2438,7 @@ export default {
panelStyleClass() {
return ['p-datepicker p-component', this.panelClass, {
'p-datepicker-inline': this.inline,
'p-disabled': this.$attrs.disabled,
'p-disabled': this.disabled,
'p-datepicker-timeonly': this.timeOnly,
'p-datepicker-multiple-month': this.numberOfMonths > 1,
'p-datepicker-monthpicker': (this.currentView === 'month'),
@ -2509,7 +2604,10 @@ export default {
return UniqueComponentId();
},
switchViewButtonDisabled() {
return this.numberOfMonths > 1 || this.$attrs.disabled;
return this.numberOfMonths > 1 || this.disabled;
},
panelId() {
return UniqueComponentId() + '_panel';
}
},
components: {

View File

@ -36,6 +36,23 @@ const defaultOptions = {
dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"],
monthNames: ["January","February","March","April","May","June","July","August","September","October","November","December"],
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
chooseYear: 'Choose Year',
chooseMonth: 'Choose Month',
chooseDate: 'Choose Date',
prevDecade: 'Previous Decade',
nextDecade: 'Next Decade',
prevYear: 'Previous Year',
nextYear: 'Next Year',
prevMonth: 'Previous Month',
nextMonth: 'Next Month',
prevHour: 'Previous Hour',
nextHour: 'Next Hour',
prevMinute: 'Previous Minute',
nextMinute: 'Next Minute',
prevSecond: 'Previous Second',
nextSecond: 'Next Second',
am: 'am',
pm: 'pm',
today: 'Today',
weekHeader: 'Wk',
firstDayOfWeek: 0,

View File

@ -628,6 +628,191 @@ export default {
</table>
</div>
<h5>Accessibility</h5>
<DevelopmentSection>
<h6>Screen Reader</h6>
<p>Value to describe the component can either be provided via <i>label</i> tag combined with <i>inputId</i> prop or using <i>aria-labelledby</i>, <i>aria-label</i> props. The input element has <i>combobox</i> role
in addition to <i>aria-autocomplete</i> as "none", <i>aria-haspopup</i> as "dialog" and <i>aria-expanded</i> attributes. The relation between the input and the popup is created with <i>aria-controls</i> attribute that refers to the id of the popup.</p>
<p>The optional calendar button requires includes <i>aria-haspopup</i>, <i>aria-expanded</i> for states along with <i>aria-controls</i> to define the relation between the popup and the button. The value to read is retrieved from the <i>chooseDate</i>
key of the aria property from the <router-link to="/locale">locale</router-link> API. This label is also used for the <i>aria-label</i> of the popup as well. When there is a value selected, it is formatted and appended to the label to be able to notify users
about the current value.</p>
<p>Popup has a <i>dialog</i> role along with <i>aria-modal</i> and <i>aria-label</i>. The navigation buttons at the header has an <i>aria-label</i> retrieved from the <i>prevYear</i>, <i>nextYear</i>, <i>prevMonth</i>, <i>nextMonth</i>,
<i>prevDecade</i> and <i>nextDecade</i> keys of the locale aria API. Similarly month picker button uses the <i>chooseMonth</i> and year picker button uses the <i>chooseYear</i> keys.</p>
<p>Main date table uses <i>grid</i> role that contains th elements with <i>col</i> as the scope along with <i>abbr</i> tag resolving to the full name of the month. Each date cell has an <i>aria-label</i> referring to the full date value.
Buttons at the footer utilize their readable labels as <i>aria-label</i> as well. Selected date also receives the <i>aria-selected</i> attribute.</p>
<p>Timepicker spinner buttons get their labels for <i>aria-label</i> from the aria locale API using the <i>prevHour</i>, <i>nextHour</i>, <i>prevMinute</i>, <i>nextMinute</i>, <i>prevSecond</i>, <i>nextSecond</i>, <i>am</i> and <i>pm</i> keys.</p>
<p>Calendar also includes a hidden section that is only available to screen readers with <i>aria-live</i> as "polite". This element is updated when the selected date changes to instruct the user about the current date selected.</p>
<pre v-code><code>
&lt;label for="date1"&gt;Date&lt;/label&gt;
&lt;Calendar inputId="date1" /&gt;
&lt;span id="date2"&gt;Date&lt;/span&gt;
&lt;Calendar aria-labelledby="date2" /&gt;
&lt;Calendar aria-label="Date" /&gt;
</code></pre>
<h6>Choose Date Button Keyboard Support</h6>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><i>space</i></td>
<td>Opens popup and moves focus to the selected date, if there is none focuses on today.</td>
</tr>
<tr>
<td><i>enter</i></td>
<td>Opens popup and moves focus to the selected date, if there is none focuses on today.</td>
</tr>
</tbody>
</table>
</div>
<h6>Popup Keyboard Support</h6>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><i>escape</i></td>
<td>Closes the popup and moves focus to the input element.</td>
</tr>
<tr>
<td><i>tab</i></td>
<td>Moves focus to the next focusable element within the popup.</td>
</tr>
<tr>
<td><i>shift</i> + <i>tab</i></td>
<td>Moves focus to the next focusable element within the popup.</td>
</tr>
</tbody>
</table>
</div>
<h6>Header Buttons Keyboard Support</h6>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><i>enter</i></td>
<td>Triggers the button action.</td>
</tr>
<tr>
<td><i>space</i></td>
<td>Triggers the button action.</td>
</tr>
</tbody>
</table>
</div>
<h6>Date Grid Keyboard Support</h6>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><i>enter</i></td>
<td>Selects the date, closes the popup and moves focus to the input element.</td>
</tr>
<tr>
<td><i>space</i></td>
<td>Selects the date, closes the popup and moves focus to the input element.</td>
</tr>
<tr>
<td><i>up arrow</i></td>
<td>Moves focus to the same day of the previous week.</td>
</tr>
<tr>
<td><i>down arrow</i></td>
<td>Moves focus to the same day of the next week.</td>
</tr>
<tr>
<td><i>right arrow</i></td>
<td>Moves focus to the next day.</td>
</tr>
<tr>
<td><i>left arrow</i></td>
<td>Moves focus to the previous day.</td>
</tr>
<tr>
<td><i>home</i></td>
<td>Moves focus to the first day of the current week.</td>
</tr>
<tr>
<td><i>end</i></td>
<td>Moves focus to the last day of the current week.</td>
</tr>
<tr>
<td><i>page up</i></td>
<td>Changes the date to previous month in date picker mode. Moves to previous year in month picker mode and previous decade in year picker.</td>
</tr>
<tr>
<td><i>shift</i> + <i>page up</i></td>
<td>Changes the date to previous year in date picker mode. Has no effect in month or year picker</td>
</tr>
<tr>
<td><i>page down</i></td>
<td>Changes the date to next month in date picker mode. Moves to next year in month picker mode and next decade in year picker.</td>
</tr>
<tr>
<td><i>shift</i> + <i>page down</i></td>
<td>Changes the date to next year in date picker mode. Has no effect in month or year picker</td>
</tr>
</tbody>
</table>
</div>
<h6>Footer Buttons Keyboard Support</h6>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><i>enter</i></td>
<td>Triggers the button action.</td>
</tr>
<tr>
<td><i>space</i></td>
<td>Triggers the button action.</td>
</tr>
</tbody>
</table>
</div>
</DevelopmentSection>
<h5>Dependencies</h5>
<p>None.</p>
</AppDoc>