feat: enable navigation to, selecting & give accessible message for calendar disabled dates (#1978)
Co-authored-by: Konstantinos Norgias <Konstantinos.Norgias@ing.com>
This commit is contained in:
parent
137a1b6cf5
commit
b44bfc5d1f
14 changed files with 619 additions and 341 deletions
9
.changeset/blue-boats-arrive.md
Normal file
9
.changeset/blue-boats-arrive.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
'@lion/ui': patch
|
||||
---
|
||||
|
||||
[calendar] updates:
|
||||
|
||||
- Enables focus to disabled dates to make it more reasonable for screen readers
|
||||
- Do not automatically force selection of a valid date
|
||||
- Add helper functions to find next/previous/nearest enabled date
|
||||
|
|
@ -190,3 +190,42 @@ export const combinedDisabledDates = () => {
|
|||
`;
|
||||
};
|
||||
```
|
||||
|
||||
### Finding enabled dates
|
||||
|
||||
The next available date may be multiple days/month in the future/past.
|
||||
For that we offer convenient helpers as
|
||||
|
||||
- `findNextEnabledDate()`
|
||||
- `findPreviousEnabledDate()`
|
||||
- `findNearestEnabledDate()`
|
||||
|
||||
```js preview-story
|
||||
export const findingEnabledDates = () => {
|
||||
function getCalendar(ev) {
|
||||
return ev.target.parentElement.querySelector('.js-calendar');
|
||||
}
|
||||
return html`
|
||||
<style>
|
||||
.demo-calendar {
|
||||
border: 1px solid #adadad;
|
||||
box-shadow: 0 0 16px #ccc;
|
||||
max-width: 500px;
|
||||
}
|
||||
</style>
|
||||
<lion-calendar
|
||||
class="demo-calendar js-calendar"
|
||||
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
|
||||
></lion-calendar>
|
||||
<button @click="${ev => getCalendar(ev).focusDate(getCalendar(ev).findNextEnabledDate())}">
|
||||
focus findNextEnabledDate
|
||||
</button>
|
||||
<button @click="${ev => getCalendar(ev).focusDate(getCalendar(ev).findPreviousEnabledDate())}">
|
||||
focus findPreviousEnabledDate
|
||||
</button>
|
||||
<button @click="${ev => getCalendar(ev).focusDate(getCalendar(ev).findNearestEnabledDate())}">
|
||||
focus findNearestEnabledDate
|
||||
</button>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { MinMaxDate, IsDateDisabled } from '@lion/ui/form-core.js';
|
|||
import { loadDefaultFeedbackMessages } from '@lion/ui/validate-messages.js';
|
||||
import { formatDate } from '@lion/ui/localize.js';
|
||||
import '@lion/ui/define/lion-input-datepicker.js';
|
||||
loadDefaultFeedbackMessages();
|
||||
```
|
||||
|
||||
## Minimum and maximum date
|
||||
|
|
@ -35,6 +36,7 @@ export const disableSpecificDates = () => html`
|
|||
<lion-input-datepicker
|
||||
label="IsDateDisabled"
|
||||
help-text="You're not allowed to choose any 15th."
|
||||
.modelValue=${new Date('2023/06/15')}
|
||||
.validators=${[new IsDateDisabled(d => d.getDate() === 15)]}
|
||||
></lion-input-datepicker>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { dayTemplate } from './utils/dayTemplate.js';
|
|||
import { getFirstDayNextMonth } from './utils/getFirstDayNextMonth.js';
|
||||
import { getLastDayPreviousMonth } from './utils/getLastDayPreviousMonth.js';
|
||||
import { isSameDate } from './utils/isSameDate.js';
|
||||
import { getDayMonthYear } from './utils/getDayMonthYear.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/day.js').Day} Day
|
||||
|
|
@ -22,6 +23,17 @@ import { isSameDate } from './utils/isSameDate.js';
|
|||
* @typedef {import('../types/day.js').Month} Month
|
||||
*/
|
||||
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} el
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDisabledDayButton(el) {
|
||||
return el.getAttribute('aria-disabled') === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* @customElement lion-calendar
|
||||
*/
|
||||
|
|
@ -213,19 +225,19 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
goToNextMonth() {
|
||||
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
|
||||
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month' });
|
||||
}
|
||||
|
||||
goToPreviousMonth() {
|
||||
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
|
||||
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month' });
|
||||
}
|
||||
|
||||
goToNextYear() {
|
||||
this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' });
|
||||
this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear' });
|
||||
}
|
||||
|
||||
goToPreviousYear() {
|
||||
this.__modifyDate(-1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' });
|
||||
this.__modifyDate(-1, { dateType: 'centralDate', type: 'FullYear' });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -238,9 +250,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
focusCentralDate() {
|
||||
const button = /** @type {HTMLElement} */ (
|
||||
this.shadowRoot?.querySelector('button[tabindex="0"]')
|
||||
);
|
||||
const button = /** @type {HTMLElement} */ (this.shadowRoot?.querySelector('[tabindex="0"]'));
|
||||
button.focus();
|
||||
this.__focusedDate = this.centralDate;
|
||||
}
|
||||
|
|
@ -328,13 +338,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
return;
|
||||
}
|
||||
|
||||
const map = {
|
||||
disableDates: () => this.__disableDatesChanged(),
|
||||
centralDate: () => this.__centralDateChanged(),
|
||||
__focusedDate: () => this.__focusedDateChanged(),
|
||||
};
|
||||
if (map[name]) {
|
||||
map[name]();
|
||||
if (name === '__focusedDate') {
|
||||
this.__focusedDateChanged();
|
||||
}
|
||||
|
||||
const updateDataOn = ['centralDate', 'minDate', 'maxDate', 'selectedDate', 'disableDates'];
|
||||
|
|
@ -362,10 +367,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
*/
|
||||
__calculateInitialCentralDate() {
|
||||
if (this.centralDate === this.__today && this.selectedDate) {
|
||||
// initialised with selectedDate only if user didn't provide another one
|
||||
// initialized with selectedDate only if user didn't provide another one
|
||||
this.centralDate = this.selectedDate;
|
||||
} else {
|
||||
this.__ensureValidCentralDate();
|
||||
}
|
||||
/** @type {Date} */
|
||||
this.__initialCentralDate = this.centralDate;
|
||||
|
|
@ -581,6 +584,35 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
return `${this.msgLit(`lion-calendar:${mode}${type}`)}, ${month} ${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
__getSelectableDateRange() {
|
||||
const newMinDate = createDay(new Date(this.minDate));
|
||||
const newMaxDate = createDay(new Date(this.maxDate));
|
||||
|
||||
const getSelectableDate = (/** @type {import("../types/day.js").Day} */ date) => {
|
||||
const { dayNumber, monthName, year } = getDayMonthYear(
|
||||
date,
|
||||
getWeekdayNames({
|
||||
locale: this.__getLocale(),
|
||||
style: 'long',
|
||||
firstDayOfWeek: this.firstDayOfWeek,
|
||||
}),
|
||||
);
|
||||
return `${dayNumber} ${monthName} ${year}`;
|
||||
};
|
||||
|
||||
const earliestSelectableDate = getSelectableDate(newMinDate);
|
||||
const latestSelectableDate = getSelectableDate(newMaxDate);
|
||||
|
||||
return {
|
||||
earliestSelectableDate,
|
||||
latestSelectableDate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Day} _day
|
||||
|
|
@ -605,13 +637,27 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
day.tabindex = day.central ? '0' : '-1';
|
||||
day.ariaPressed = day.selected ? 'true' : 'false';
|
||||
day.ariaCurrent = day.today ? 'date' : undefined;
|
||||
day.disabledInfo = '';
|
||||
|
||||
if (this.minDate && normalizeDateTime(day.date) < normalizeDateTime(this.minDate)) {
|
||||
day.disabled = true;
|
||||
// TODO: turn this into a translated string
|
||||
day.disabledInfo = `This date is unavailable. Earliest date to select is ${
|
||||
this.__getSelectableDateRange().earliestSelectableDate
|
||||
}. Please select another date.`;
|
||||
}
|
||||
|
||||
if (this.maxDate && normalizeDateTime(day.date) > normalizeDateTime(this.maxDate)) {
|
||||
day.disabled = true;
|
||||
// TODO: turn this into a translated string
|
||||
day.disabledInfo = `This date is unavailable. Latest date to select is ${
|
||||
this.__getSelectableDateRange().latestSelectableDate
|
||||
}. Please select another date.`;
|
||||
}
|
||||
|
||||
if (day.disabled) {
|
||||
// TODO: turn this into a translated string
|
||||
day.disabledInfo = `This date is unavailable. Please select another date`;
|
||||
}
|
||||
|
||||
return this.dayPreprocessor(day);
|
||||
|
|
@ -641,15 +687,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__disableDatesChanged() {
|
||||
if (this.__connectedCallbackDone) {
|
||||
this.__ensureValidCentralDate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} selectedDate
|
||||
* @private
|
||||
|
|
@ -666,15 +703,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__centralDateChanged() {
|
||||
if (this.__connectedCallbackDone) {
|
||||
this.__ensureValidCentralDate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
|
@ -685,12 +713,30 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Date} [date]
|
||||
* @returns
|
||||
*/
|
||||
__ensureValidCentralDate() {
|
||||
if (!this.__isEnabledDate(this.centralDate)) {
|
||||
this.centralDate = this.__findBestEnabledDateFor(this.centralDate);
|
||||
findNextEnabledDate(date) {
|
||||
const _date = date || this.centralDate;
|
||||
return this.__findBestEnabledDateFor(_date, { mode: 'future' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} [date]
|
||||
* @returns
|
||||
*/
|
||||
findPreviousEnabledDate(date) {
|
||||
const _date = date || this.centralDate;
|
||||
return this.__findBestEnabledDateFor(_date, { mode: 'past' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} [date]
|
||||
* @returns
|
||||
*/
|
||||
findNearestEnabledDate(date) {
|
||||
const _date = date || this.centralDate;
|
||||
return this.__findBestEnabledDateFor(_date, { mode: 'both' });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -750,11 +796,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* @private
|
||||
*/
|
||||
__clickDateDelegation(ev) {
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
const el = /** @type {HTMLElement & { date: Date }} */ (ev.composedPath()[0]);
|
||||
if (isDayButton(el)) {
|
||||
const el = /** @type {HTMLElement & { date: Date }} */ (ev.target);
|
||||
if (isDayButton(el) && !isDisabledDayButton(el)) {
|
||||
this.__dateSelectedByUser(el.date);
|
||||
}
|
||||
}
|
||||
|
|
@ -763,9 +806,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* @private
|
||||
*/
|
||||
__focusDateDelegation() {
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
if (
|
||||
!this.__focusedDate &&
|
||||
isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
|
||||
|
|
@ -780,9 +820,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* @private
|
||||
*/
|
||||
__blurDateDelegation() {
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
setTimeout(() => {
|
||||
if (
|
||||
this.shadowRoot?.activeElement &&
|
||||
|
|
@ -793,42 +830,65 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement & { date: Date }} el
|
||||
* @private
|
||||
*/
|
||||
__dayButtonSelection(el) {
|
||||
if (isDayButton(el)) {
|
||||
this.__dateSelectedByUser(el.date);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} ev
|
||||
* @private
|
||||
*/
|
||||
__keyboardNavigationEvent(ev) {
|
||||
const preventedKeys = ['ArrowUp', 'ArrowDown', 'PageDown', 'PageUp'];
|
||||
const preventedKeys = [
|
||||
'ArrowLeft',
|
||||
'ArrowUp',
|
||||
'ArrowRight',
|
||||
'ArrowDown',
|
||||
'PageDown',
|
||||
'PageUp',
|
||||
' ',
|
||||
'Enter',
|
||||
];
|
||||
|
||||
if (preventedKeys.includes(ev.key)) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
switch (ev.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.__dayButtonSelection(/** @type {HTMLElement & { date: Date }} */ (ev.target));
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
|
||||
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date' });
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
|
||||
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date' });
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
|
||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date' });
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
|
||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date' });
|
||||
break;
|
||||
case 'PageDown':
|
||||
if (ev.altKey === true) {
|
||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear', mode: 'future' });
|
||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear' });
|
||||
} else {
|
||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month', mode: 'future' });
|
||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month' });
|
||||
}
|
||||
break;
|
||||
case 'PageUp':
|
||||
if (ev.altKey === true) {
|
||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear', mode: 'past' });
|
||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear' });
|
||||
} else {
|
||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month', mode: 'past' });
|
||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month' });
|
||||
}
|
||||
break;
|
||||
case 'Tab':
|
||||
|
|
@ -844,11 +904,10 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* @param {Object} opts
|
||||
* @param {string} opts.dateType
|
||||
* @param {string} opts.type
|
||||
* @param {string} opts.mode
|
||||
* @private
|
||||
*/
|
||||
__modifyDate(modify, { dateType, type, mode }) {
|
||||
let tmpDate = new Date(this.centralDate);
|
||||
__modifyDate(modify, { dateType, type }) {
|
||||
const tmpDate = new Date(this.centralDate);
|
||||
// if we're not working with days, reset
|
||||
// day count to first day of the month
|
||||
if (type !== 'Date') {
|
||||
|
|
@ -861,9 +920,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
const maxDays = new Date(tmpDate.getFullYear(), tmpDate.getMonth() + 1, 0).getDate();
|
||||
tmpDate.setDate(Math.min(this.centralDate.getDate(), maxDays));
|
||||
}
|
||||
if (!this.__isEnabledDate(tmpDate)) {
|
||||
tmpDate = this.__findBestEnabledDateFor(tmpDate, { mode });
|
||||
}
|
||||
this[dateType] = tmpDate;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@ export const calendarStyle = css`
|
|||
padding: 0;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
/** give div[role=button][aria-disabled] same display type as native btn */
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.calendar__day-button:focus {
|
||||
border: 1px solid blue;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.calendar__day-button__text {
|
||||
|
|
@ -77,9 +87,23 @@ export const calendarStyle = css`
|
|||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.calendar__day-button[disabled] {
|
||||
.calendar__day-button[aria-disabled='true'] {
|
||||
background-color: #fff;
|
||||
color: #eee;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.u-sr-only {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(100%);
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export function createDay(
|
|||
today = false,
|
||||
future = false,
|
||||
disabled = false,
|
||||
disabledInfo = '',
|
||||
} = {},
|
||||
) {
|
||||
return {
|
||||
|
|
@ -34,5 +35,6 @@ export function createDay(
|
|||
tabindex: '-1',
|
||||
ariaPressed: 'false',
|
||||
ariaCurrent: undefined,
|
||||
disabledInfo,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,7 @@
|
|||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { defaultMonthLabels, getDayMonthYear } from './getDayMonthYear.js';
|
||||
|
||||
const defaultMonthLabels = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
const firstWeekDays = [1, 2, 3, 4, 5, 6, 7];
|
||||
const lastDaysOfYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
|
|
@ -24,10 +11,15 @@ const lastDaysOfYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|||
* @param {{ weekdays: string[], monthsLabels?: string[] }} opts
|
||||
*/
|
||||
export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }) {
|
||||
const dayNumber = day.date.getDate();
|
||||
const monthName = monthsLabels[day.date.getMonth()];
|
||||
const year = day.date.getFullYear();
|
||||
const weekdayName = day.weekOrder ? weekdays[day.weekOrder] : weekdays[0];
|
||||
const { dayNumber, monthName, year, weekdayName } = getDayMonthYear(day, weekdays, monthsLabels);
|
||||
|
||||
function __getFullDate() {
|
||||
return `${monthName} ${year} ${weekdayName}`;
|
||||
}
|
||||
|
||||
function __getAccessibleMessage() {
|
||||
return `${day.disabledInfo}`;
|
||||
}
|
||||
|
||||
const firstDay = dayNumber === 1;
|
||||
const endOfFirstWeek = day.weekOrder === 6 && firstWeekDays.includes(dayNumber);
|
||||
|
|
@ -57,20 +49,14 @@ export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }
|
|||
?start-of-last-week=${startOfLastWeek}
|
||||
?last-day=${lastDay}
|
||||
>
|
||||
<button
|
||||
<div
|
||||
role="button"
|
||||
.date=${day.date}
|
||||
class="calendar__day-button"
|
||||
tabindex=${ifDefined(Number(day.tabindex))}
|
||||
aria-label=${`${dayNumber} ${monthName} ${year} ${weekdayName}`}
|
||||
aria-pressed=${
|
||||
/** @type {'true'|'false'|'mixed'|'undefined'} */ (ifDefined(day.ariaPressed))
|
||||
}
|
||||
aria-current=${
|
||||
/** @type {'page'|'step'|'location'|'date'|'time'|'true'|'false'} */ (
|
||||
ifDefined(day.ariaCurrent)
|
||||
)
|
||||
}
|
||||
?disabled=${day.disabled}
|
||||
tabindex=${ifDefined(day.tabindex)}
|
||||
aria-pressed=${ifDefined(day.ariaPressed)}
|
||||
aria-current=${ifDefined(day.ariaCurrent)}
|
||||
aria-disabled=${day.disabled ? 'true' : 'false'}
|
||||
?selected=${day.selected}
|
||||
?past=${day.past}
|
||||
?today=${day.today}
|
||||
|
|
@ -79,8 +65,9 @@ export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }
|
|||
?current-month=${day.currentMonth}
|
||||
?next-month=${day.nextMonth}
|
||||
>
|
||||
<span class="calendar__day-button__text"> ${day.date.getDate()} </span>
|
||||
</button>
|
||||
<span class="calendar__day-button__text">${dayNumber}</span>
|
||||
<span class="u-sr-only">${__getFullDate()} ${__getAccessibleMessage()}</span>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
|
|
|
|||
28
packages/ui/components/calendar/src/utils/getDayMonthYear.js
Normal file
28
packages/ui/components/calendar/src/utils/getDayMonthYear.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export const defaultMonthLabels = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {import('../../types/day.js').Day} day
|
||||
* @param {string[]} weekdays
|
||||
* @param {string[] } monthsLabels
|
||||
*/
|
||||
export function getDayMonthYear(day, weekdays, monthsLabels = defaultMonthLabels) {
|
||||
const dayNumber = day.date.getDate();
|
||||
const monthName = monthsLabels[day.date.getMonth()];
|
||||
const year = day.date.getFullYear();
|
||||
const weekdayName = day.weekOrder ? weekdays[day.weekOrder] : weekdays[0];
|
||||
|
||||
return { dayNumber, monthName, year, weekdayName };
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ export class DayObject {
|
|||
*/
|
||||
|
||||
get isDisabled() {
|
||||
return this.buttonEl.hasAttribute('disabled');
|
||||
return this.buttonEl.getAttribute('aria-disabled') === 'true';
|
||||
}
|
||||
|
||||
get isSelected() {
|
||||
|
|
@ -54,7 +54,7 @@ export class DayObject {
|
|||
}
|
||||
|
||||
get monthday() {
|
||||
return Number(this.buttonEl.textContent);
|
||||
return Number(this.buttonEl.children[0].textContent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ describe('<lion-calendar>', () => {
|
|||
clock.restore();
|
||||
});
|
||||
|
||||
it('should set centralDate to the unique valid value when minDate and maxDate are equal', async () => {
|
||||
it('requires the user to set an appropriate centralDate even when minDate and maxDate are equal', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2019/06/03').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
|
|
@ -405,9 +405,18 @@ describe('<lion-calendar>', () => {
|
|||
.maxDate="${new Date('2019/07/03')}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
expect(isSameDate(el.centralDate, new Date('2019/07/03')), 'central date').to.be.true;
|
||||
const elSetting = await fixture(html`
|
||||
<lion-calendar
|
||||
.centralDate="${new Date('2019/07/03')}"
|
||||
.minDate="${new Date('2019/07/03')}"
|
||||
.maxDate="${new Date('2019/07/03')}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
|
||||
clock.restore();
|
||||
expect(isSameDate(el.centralDate, new Date('2019/06/03')), 'central date').to.be.true;
|
||||
expect(isSameDate(elSetting.centralDate, new Date('2019/07/03')), 'central date').to.be
|
||||
.true;
|
||||
});
|
||||
|
||||
describe('Normalization', () => {
|
||||
|
|
@ -482,6 +491,98 @@ describe('<lion-calendar>', () => {
|
|||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
describe('finding enabled dates', () => {
|
||||
it('has helper for `findNextEnabledDate()`, `findPreviousEnabledDate()`, `findNearestEnabledDate()`', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.selectedDate="${new Date('2001/01/02')}"
|
||||
.disableDates=${
|
||||
/** @param {Date} date */ date => date.getDate() === 3 || date.getDate() === 4
|
||||
}
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
||||
el.focusDate(el.findNextEnabledDate());
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(5);
|
||||
|
||||
el.focusDate(el.findPreviousEnabledDate());
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(2);
|
||||
|
||||
el.focusDate(el.findNearestEnabledDate());
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('future dates take precedence over past dates when "distance" between dates is equal', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getDate() === 15}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
el.focusDate(el.findNearestEnabledDate());
|
||||
await el.updateComplete;
|
||||
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(16);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('will search 750 days in the past', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() > 1998}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
el.focusDate(el.findNearestEnabledDate());
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.centralDate.getFullYear()).to.equal(1998);
|
||||
expect(el.centralDate.getMonth()).to.equal(11);
|
||||
expect(el.centralDate.getDate()).to.equal(31);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('will search 750 days in the future', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() < 2002}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
|
||||
el.focusDate(el.findNearestEnabledDate());
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.centralDate.getFullYear()).to.equal(2002);
|
||||
expect(el.centralDate.getMonth()).to.equal(0);
|
||||
expect(el.centralDate.getDate()).to.equal(1);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('throws if no available date can be found within +/- 750 days', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() < 2002}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
|
||||
expect(() => {
|
||||
el.findNextEnabledDate(new Date('1900/01/01'));
|
||||
}).to.throw(Error, 'Could not find a selectable date within +/- 750 day for 1900/1/1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Year', () => {
|
||||
it('has a button for navigation to previous year', async () => {
|
||||
const el = await fixture(
|
||||
|
|
@ -655,7 +756,7 @@ describe('<lion-calendar>', () => {
|
|||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('November');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
expect(isSameDate(el.centralDate, new Date('2000/11/20'))).to.be.true;
|
||||
expect(isSameDate(el.centralDate, new Date('2000/11/15'))).to.be.true;
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
|
@ -677,7 +778,7 @@ describe('<lion-calendar>', () => {
|
|||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
expect(isSameDate(el.centralDate, new Date('2001/01/10'))).to.be.true;
|
||||
expect(isSameDate(el.centralDate, new Date('2001/01/15'))).to.be.true;
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
|
@ -696,17 +797,21 @@ describe('<lion-calendar>', () => {
|
|||
expect(remote.activeMonth).to.equal('September');
|
||||
expect(remote.activeYear).to.equal('2019');
|
||||
expect(remote.centralDayObj?.el).dom.to.equal(`
|
||||
<button
|
||||
<div
|
||||
class="calendar__day-button"
|
||||
aria-disabled="false"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="30 September 2019 Monday"
|
||||
aria-pressed="false"
|
||||
past=""
|
||||
current-month="">
|
||||
<span class="calendar__day-button__text">
|
||||
30
|
||||
</span>
|
||||
</button>
|
||||
<span class="u-sr-only">
|
||||
September 2019 Monday
|
||||
</span>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
@ -801,7 +906,7 @@ describe('<lion-calendar>', () => {
|
|||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('adds "disabled" attribute to disabled dates', async () => {
|
||||
it('adds aria-disabled="true" attribute to disabled dates', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
|
|
@ -815,7 +920,7 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
/** @param {DayObject} d */ d => d.el.hasAttribute('disabled'),
|
||||
/** @param {DayObject} d */ d => d.el.getAttribute('aria-disabled') === 'true',
|
||||
[1, 2, 30, 31],
|
||||
),
|
||||
).to.equal(true);
|
||||
|
|
@ -972,7 +1077,7 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.focusedDayObj?.monthday).to.equal(12 + 1);
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to next selectable column item via [arrow right] key', async () => {
|
||||
it('navigates (sets focus) to next column item via [arrow right] key', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.selectedDate="${new Date('2001/01/02')}"
|
||||
|
|
@ -987,7 +1092,7 @@ describe('<lion-calendar>', () => {
|
|||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(5);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(3);
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to next row via [arrow right] key if last item in row', async () => {
|
||||
|
|
@ -1108,77 +1213,6 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('is on day closest to today, if today (and surrounding dates) is/are disabled', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.centralDate="${new Date('2000/12/15')}"
|
||||
.disableDates="${/** @param {Date} d */ d => d.getDate() <= 16}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(17);
|
||||
|
||||
el.disableDates = d => d.getDate() >= 12;
|
||||
await el.updateComplete;
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(11);
|
||||
});
|
||||
|
||||
it('future dates take precedence over past dates when "distance" between dates is equal', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getDate() === 15}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(16);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('will search 750 days in the past', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() > 1998}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
expect(el.centralDate.getFullYear()).to.equal(1998);
|
||||
expect(el.centralDate.getMonth()).to.equal(11);
|
||||
expect(el.centralDate.getDate()).to.equal(31);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('will search 750 days in the future', async () => {
|
||||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() < 2002}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
expect(el.centralDate.getFullYear()).to.equal(2002);
|
||||
expect(el.centralDate.getMonth()).to.equal(0);
|
||||
expect(el.centralDate.getDate()).to.equal(1);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('throws if no available date can be found within +/- 750 days', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() < 2002}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
|
||||
expect(() => {
|
||||
el.centralDate = new Date('1900/01/01');
|
||||
}).to.throw(Error, 'Could not find a selectable date within +/- 750 day for 1900/1/1');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -1226,7 +1260,8 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
it('renders each day as a button inside a table cell', async () => {
|
||||
const elObj = new CalendarObject(await fixture(html`<lion-calendar></lion-calendar>`));
|
||||
const hasBtn = /** @param {DayObject} d */ d => d.el.tagName === 'BUTTON';
|
||||
const hasBtn = /** @param {DayObject} d */ d =>
|
||||
d.el.tagName === 'DIV' && d.el.getAttribute('role') === 'button';
|
||||
expect(elObj.checkForAllDayObjs(hasBtn)).to.equal(true);
|
||||
});
|
||||
|
||||
|
|
@ -1301,31 +1336,6 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.checkForAllDayObjs(hasAriaPressed, [12])).to.equal(true);
|
||||
});
|
||||
|
||||
// This implementation mentions "button" inbetween and doesn't mention table
|
||||
// column and row. As an alternative, see Deque implementation below.
|
||||
// it(`on focus on a day, the screen reader pronounces "day of the week", "day number"
|
||||
// and "month" (in this order)', async () => {
|
||||
// // implemented by labelelledby referencing row and column names
|
||||
// const el = await fixture('<lion-calendar></lion-calendar>');
|
||||
// });
|
||||
|
||||
// Alternative: Deque implementation
|
||||
it(`sets aria-label on button, that consists of
|
||||
"{day number} {month name} {year} {weekday name}"`, async () => {
|
||||
const elObj = new CalendarObject(
|
||||
await fixture(html`
|
||||
<lion-calendar .centralDate="${new Date('2000/11/12')}"></lion-calendar>
|
||||
`),
|
||||
);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
/** @param {DayObject} d */ d =>
|
||||
d.buttonEl.getAttribute('aria-label') ===
|
||||
`${d.monthday} November 2000 ${d.weekdayNameLong}`,
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Not in scope:
|
||||
* - reads the new focused day on month navigation"
|
||||
|
|
|
|||
|
|
@ -11,14 +11,18 @@ describe('dayTemplate', () => {
|
|||
const el = await fixture(dayTemplate(day, { weekdays }));
|
||||
expect(el).dom.to.equal(`
|
||||
<td role="gridcell" class="calendar__day-cell">
|
||||
<button
|
||||
class="calendar__day-button"
|
||||
aria-label="19 April 2019 Friday"
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
role="button"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">19</span>
|
||||
</button>
|
||||
<span class="u-sr-only">
|
||||
April 2019 Friday
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,434 +52,518 @@ export default html`
|
|||
<tbody>
|
||||
<tr role="row">
|
||||
<td class="calendar__day-cell" role="gridcell" start-of-last-week>
|
||||
<button
|
||||
aria-label="25 November 2018 Sunday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">25</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> November 2018 Sunday</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="26 November 2018 Monday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">26</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> November 2018 Monday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="27 November 2018 Tuesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">27</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> November 2018 Tuesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="28 November 2018 Wednesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">28</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> November 2018 Wednesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="29 November 2018 Thursday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">29</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> November 2018 Thursday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell" last-day>
|
||||
<button
|
||||
aria-label="30 November 2018 Friday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">30</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> November 2018 Friday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell" end-of-first-week first-day>
|
||||
<button
|
||||
aria-label="1 December 2018 Saturday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">1</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr role="row">
|
||||
<td class="calendar__day-cell" role="gridcell" start-of-first-full-week>
|
||||
<button
|
||||
aria-label="2 December 2018 Sunday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">2</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="3 December 2018 Monday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">3</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Monday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="4 December 2018 Tuesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">4</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="5 December 2018 Wednesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">5</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="6 December 2018 Thursday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">6</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="7 December 2018 Friday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">7</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Friday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="8 December 2018 Saturday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">8</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr role="row">
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="9 December 2018 Sunday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">9</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="10 December 2018 Monday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">10</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Monday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="11 December 2018 Tuesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">11</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="12 December 2018 Wednesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">12</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="13 December 2018 Thursday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">13</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="14 December 2018 Friday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">14</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Friday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="15 December 2018 Saturday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">15</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr role="row">
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="16 December 2018 Sunday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">16</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="17 December 2018 Monday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">17</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Monday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="18 December 2018 Tuesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">18</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="19 December 2018 Wednesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">19</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="20 December 2018 Thursday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">20</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="21 December 2018 Friday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">21</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Friday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="22 December 2018 Saturday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">22</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr role="row">
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="23 December 2018 Sunday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">23</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="24 December 2018 Monday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">24</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Monday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="25 December 2018 Tuesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">25</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="26 December 2018 Wednesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">26</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="27 December 2018 Thursday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">27</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="28 December 2018 Friday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">28</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Friday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell" end-of-last-full-week>
|
||||
<button
|
||||
aria-label="29 December 2018 Saturday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">29</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr role="row">
|
||||
<td class="calendar__day-cell" role="gridcell" start-of-last-week>
|
||||
<button
|
||||
aria-label="30 December 2018 Sunday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">30</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" last-day role="gridcell">
|
||||
<button
|
||||
aria-label="31 December 2018 Monday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">31</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> December 2018 Monday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell" first-day>
|
||||
<button
|
||||
aria-label="1 January 2019 Tuesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">1</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> January 2019 Tuesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="2 January 2019 Wednesday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">2</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> January 2019 Wednesday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="3 January 2019 Thursday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">3</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> January 2019 Thursday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell">
|
||||
<button
|
||||
aria-label="4 January 2019 Friday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">4</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> January 2019 Friday </span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="calendar__day-cell" role="gridcell" end-of-first-week>
|
||||
<button
|
||||
aria-label="5 January 2019 Saturday"
|
||||
<div
|
||||
role="button"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
class="calendar__day-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="calendar__day-button__text">5</span>
|
||||
</button>
|
||||
<span class="u-sr-only"> January 2019 Saturday </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export declare interface Day {
|
|||
tabindex?: string;
|
||||
ariaPressed?: string;
|
||||
ariaCurrent?: string | undefined;
|
||||
disabledInfo?: string | undefined;
|
||||
}
|
||||
|
||||
export declare interface Week {
|
||||
|
|
|
|||
|
|
@ -96,10 +96,28 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|||
|
||||
__calendarDisableDates: {
|
||||
attribute: false,
|
||||
type: Array,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_inputFormatter = new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
}).formatToParts;
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_formatDate(date) {
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const year = String(date.getFullYear());
|
||||
return `${day}/${month}/${year}`;
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return {
|
||||
...super.slots,
|
||||
|
|
@ -346,6 +364,7 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|||
}
|
||||
|
||||
/**
|
||||
* Triggered when a user selects a date from the calendar overlay
|
||||
* @param {{ target: { selectedDate: Date }}} opts
|
||||
*/
|
||||
_onCalendarUserSelectedChanged({ target: { selectedDate } }) {
|
||||
|
|
@ -355,8 +374,21 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|||
if (this._syncOnUserSelect) {
|
||||
// Synchronize new selectedDate value to input
|
||||
this._isHandlingUserInput = true;
|
||||
this._isHandlingCalendarUserInput = true;
|
||||
|
||||
if (
|
||||
Array.isArray(this.__calendarDisableDates) &&
|
||||
this.__calendarDisableDates.includes(selectedDate)
|
||||
) {
|
||||
// If the selected date is disabled, reset the values
|
||||
this.value = '';
|
||||
this.formattedValue = '';
|
||||
this.modelValue = undefined;
|
||||
} else {
|
||||
this.formattedValue = this._formatDate(selectedDate);
|
||||
this.value = this.formattedValue;
|
||||
this.modelValue = selectedDate;
|
||||
}
|
||||
|
||||
this._isHandlingUserInput = false;
|
||||
this._isHandlingCalendarUserInput = false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue