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 { loadDefaultFeedbackMessages } from '@lion/ui/validate-messages.js';
|
||||||
import { formatDate } from '@lion/ui/localize.js';
|
import { formatDate } from '@lion/ui/localize.js';
|
||||||
import '@lion/ui/define/lion-input-datepicker.js';
|
import '@lion/ui/define/lion-input-datepicker.js';
|
||||||
|
loadDefaultFeedbackMessages();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Minimum and maximum date
|
## Minimum and maximum date
|
||||||
|
|
@ -35,6 +36,7 @@ export const disableSpecificDates = () => html`
|
||||||
<lion-input-datepicker
|
<lion-input-datepicker
|
||||||
label="IsDateDisabled"
|
label="IsDateDisabled"
|
||||||
help-text="You're not allowed to choose any 15th."
|
help-text="You're not allowed to choose any 15th."
|
||||||
|
.modelValue=${new Date('2023/06/15')}
|
||||||
.validators=${[new IsDateDisabled(d => d.getDate() === 15)]}
|
.validators=${[new IsDateDisabled(d => d.getDate() === 15)]}
|
||||||
></lion-input-datepicker>
|
></lion-input-datepicker>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { dayTemplate } from './utils/dayTemplate.js';
|
||||||
import { getFirstDayNextMonth } from './utils/getFirstDayNextMonth.js';
|
import { getFirstDayNextMonth } from './utils/getFirstDayNextMonth.js';
|
||||||
import { getLastDayPreviousMonth } from './utils/getLastDayPreviousMonth.js';
|
import { getLastDayPreviousMonth } from './utils/getLastDayPreviousMonth.js';
|
||||||
import { isSameDate } from './utils/isSameDate.js';
|
import { isSameDate } from './utils/isSameDate.js';
|
||||||
|
import { getDayMonthYear } from './utils/getDayMonthYear.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('../types/day.js').Day} Day
|
* @typedef {import('../types/day.js').Day} Day
|
||||||
|
|
@ -22,6 +23,17 @@ import { isSameDate } from './utils/isSameDate.js';
|
||||||
* @typedef {import('../types/day.js').Month} Month
|
* @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
|
* @customElement lion-calendar
|
||||||
*/
|
*/
|
||||||
|
|
@ -213,19 +225,19 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
goToNextMonth() {
|
goToNextMonth() {
|
||||||
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
|
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month' });
|
||||||
}
|
}
|
||||||
|
|
||||||
goToPreviousMonth() {
|
goToPreviousMonth() {
|
||||||
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
|
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month' });
|
||||||
}
|
}
|
||||||
|
|
||||||
goToNextYear() {
|
goToNextYear() {
|
||||||
this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' });
|
this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear' });
|
||||||
}
|
}
|
||||||
|
|
||||||
goToPreviousYear() {
|
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() {
|
focusCentralDate() {
|
||||||
const button = /** @type {HTMLElement} */ (
|
const button = /** @type {HTMLElement} */ (this.shadowRoot?.querySelector('[tabindex="0"]'));
|
||||||
this.shadowRoot?.querySelector('button[tabindex="0"]')
|
|
||||||
);
|
|
||||||
button.focus();
|
button.focus();
|
||||||
this.__focusedDate = this.centralDate;
|
this.__focusedDate = this.centralDate;
|
||||||
}
|
}
|
||||||
|
|
@ -328,13 +338,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = {
|
if (name === '__focusedDate') {
|
||||||
disableDates: () => this.__disableDatesChanged(),
|
this.__focusedDateChanged();
|
||||||
centralDate: () => this.__centralDateChanged(),
|
|
||||||
__focusedDate: () => this.__focusedDateChanged(),
|
|
||||||
};
|
|
||||||
if (map[name]) {
|
|
||||||
map[name]();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDataOn = ['centralDate', 'minDate', 'maxDate', 'selectedDate', 'disableDates'];
|
const updateDataOn = ['centralDate', 'minDate', 'maxDate', 'selectedDate', 'disableDates'];
|
||||||
|
|
@ -362,10 +367,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
*/
|
*/
|
||||||
__calculateInitialCentralDate() {
|
__calculateInitialCentralDate() {
|
||||||
if (this.centralDate === this.__today && this.selectedDate) {
|
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;
|
this.centralDate = this.selectedDate;
|
||||||
} else {
|
|
||||||
this.__ensureValidCentralDate();
|
|
||||||
}
|
}
|
||||||
/** @type {Date} */
|
/** @type {Date} */
|
||||||
this.__initialCentralDate = this.centralDate;
|
this.__initialCentralDate = this.centralDate;
|
||||||
|
|
@ -581,6 +584,35 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
return `${this.msgLit(`lion-calendar:${mode}${type}`)}, ${month} ${year}`;
|
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
|
* @param {Day} _day
|
||||||
|
|
@ -605,13 +637,27 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
day.tabindex = day.central ? '0' : '-1';
|
day.tabindex = day.central ? '0' : '-1';
|
||||||
day.ariaPressed = day.selected ? 'true' : 'false';
|
day.ariaPressed = day.selected ? 'true' : 'false';
|
||||||
day.ariaCurrent = day.today ? 'date' : undefined;
|
day.ariaCurrent = day.today ? 'date' : undefined;
|
||||||
|
day.disabledInfo = '';
|
||||||
|
|
||||||
if (this.minDate && normalizeDateTime(day.date) < normalizeDateTime(this.minDate)) {
|
if (this.minDate && normalizeDateTime(day.date) < normalizeDateTime(this.minDate)) {
|
||||||
day.disabled = true;
|
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)) {
|
if (this.maxDate && normalizeDateTime(day.date) > normalizeDateTime(this.maxDate)) {
|
||||||
day.disabled = true;
|
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);
|
return this.dayPreprocessor(day);
|
||||||
|
|
@ -641,15 +687,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
__disableDatesChanged() {
|
|
||||||
if (this.__connectedCallbackDone) {
|
|
||||||
this.__ensureValidCentralDate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Date} selectedDate
|
* @param {Date} selectedDate
|
||||||
* @private
|
* @private
|
||||||
|
|
@ -666,15 +703,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
__centralDateChanged() {
|
|
||||||
if (this.__connectedCallbackDone) {
|
|
||||||
this.__ensureValidCentralDate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|
@ -685,12 +713,30 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @param {Date} [date]
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
__ensureValidCentralDate() {
|
findNextEnabledDate(date) {
|
||||||
if (!this.__isEnabledDate(this.centralDate)) {
|
const _date = date || this.centralDate;
|
||||||
this.centralDate = this.__findBestEnabledDateFor(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
|
* @private
|
||||||
*/
|
*/
|
||||||
__clickDateDelegation(ev) {
|
__clickDateDelegation(ev) {
|
||||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
const el = /** @type {HTMLElement & { date: Date }} */ (ev.target);
|
||||||
el.classList.contains('calendar__day-button');
|
if (isDayButton(el) && !isDisabledDayButton(el)) {
|
||||||
|
|
||||||
const el = /** @type {HTMLElement & { date: Date }} */ (ev.composedPath()[0]);
|
|
||||||
if (isDayButton(el)) {
|
|
||||||
this.__dateSelectedByUser(el.date);
|
this.__dateSelectedByUser(el.date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -763,9 +806,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__focusDateDelegation() {
|
__focusDateDelegation() {
|
||||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
|
||||||
el.classList.contains('calendar__day-button');
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.__focusedDate &&
|
!this.__focusedDate &&
|
||||||
isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
|
isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
|
||||||
|
|
@ -780,9 +820,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__blurDateDelegation() {
|
__blurDateDelegation() {
|
||||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
|
||||||
el.classList.contains('calendar__day-button');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (
|
if (
|
||||||
this.shadowRoot?.activeElement &&
|
this.shadowRoot?.activeElement &&
|
||||||
|
|
@ -793,42 +830,65 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement & { date: Date }} el
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__dayButtonSelection(el) {
|
||||||
|
if (isDayButton(el)) {
|
||||||
|
this.__dateSelectedByUser(el.date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {KeyboardEvent} ev
|
* @param {KeyboardEvent} ev
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__keyboardNavigationEvent(ev) {
|
__keyboardNavigationEvent(ev) {
|
||||||
const preventedKeys = ['ArrowUp', 'ArrowDown', 'PageDown', 'PageUp'];
|
const preventedKeys = [
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowDown',
|
||||||
|
'PageDown',
|
||||||
|
'PageUp',
|
||||||
|
' ',
|
||||||
|
'Enter',
|
||||||
|
];
|
||||||
|
|
||||||
if (preventedKeys.includes(ev.key)) {
|
if (preventedKeys.includes(ev.key)) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
|
case ' ':
|
||||||
|
case 'Enter':
|
||||||
|
this.__dayButtonSelection(/** @type {HTMLElement & { date: Date }} */ (ev.target));
|
||||||
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
|
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date' });
|
||||||
break;
|
break;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
|
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date' });
|
||||||
break;
|
break;
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
|
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date' });
|
||||||
break;
|
break;
|
||||||
case 'ArrowRight':
|
case 'ArrowRight':
|
||||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
|
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date' });
|
||||||
break;
|
break;
|
||||||
case 'PageDown':
|
case 'PageDown':
|
||||||
if (ev.altKey === true) {
|
if (ev.altKey === true) {
|
||||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear', mode: 'future' });
|
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear' });
|
||||||
} else {
|
} else {
|
||||||
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month', mode: 'future' });
|
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month' });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'PageUp':
|
case 'PageUp':
|
||||||
if (ev.altKey === true) {
|
if (ev.altKey === true) {
|
||||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear', mode: 'past' });
|
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear' });
|
||||||
} else {
|
} else {
|
||||||
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month', mode: 'past' });
|
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month' });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
|
|
@ -844,11 +904,10 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* @param {string} opts.dateType
|
* @param {string} opts.dateType
|
||||||
* @param {string} opts.type
|
* @param {string} opts.type
|
||||||
* @param {string} opts.mode
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__modifyDate(modify, { dateType, type, mode }) {
|
__modifyDate(modify, { dateType, type }) {
|
||||||
let tmpDate = new Date(this.centralDate);
|
const tmpDate = new Date(this.centralDate);
|
||||||
// if we're not working with days, reset
|
// if we're not working with days, reset
|
||||||
// day count to first day of the month
|
// day count to first day of the month
|
||||||
if (type !== 'Date') {
|
if (type !== 'Date') {
|
||||||
|
|
@ -861,9 +920,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
const maxDays = new Date(tmpDate.getFullYear(), tmpDate.getMonth() + 1, 0).getDate();
|
const maxDays = new Date(tmpDate.getFullYear(), tmpDate.getMonth() + 1, 0).getDate();
|
||||||
tmpDate.setDate(Math.min(this.centralDate.getDate(), maxDays));
|
tmpDate.setDate(Math.min(this.centralDate.getDate(), maxDays));
|
||||||
}
|
}
|
||||||
if (!this.__isEnabledDate(tmpDate)) {
|
|
||||||
tmpDate = this.__findBestEnabledDateFor(tmpDate, { mode });
|
|
||||||
}
|
|
||||||
this[dateType] = tmpDate;
|
this[dateType] = tmpDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,16 @@ export const calendarStyle = css`
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
min-height: 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 {
|
.calendar__day-button__text {
|
||||||
|
|
@ -77,9 +87,23 @@ export const calendarStyle = css`
|
||||||
border: 1px solid green;
|
border: 1px solid green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar__day-button[disabled] {
|
.calendar__day-button[aria-disabled='true'] {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
outline: none;
|
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,
|
today = false,
|
||||||
future = false,
|
future = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
disabledInfo = '',
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -34,5 +35,6 @@ export function createDay(
|
||||||
tabindex: '-1',
|
tabindex: '-1',
|
||||||
ariaPressed: 'false',
|
ariaPressed: 'false',
|
||||||
ariaCurrent: undefined,
|
ariaCurrent: undefined,
|
||||||
|
disabledInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,7 @@
|
||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
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 firstWeekDays = [1, 2, 3, 4, 5, 6, 7];
|
||||||
const lastDaysOfYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
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
|
* @param {{ weekdays: string[], monthsLabels?: string[] }} opts
|
||||||
*/
|
*/
|
||||||
export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }) {
|
export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }) {
|
||||||
const dayNumber = day.date.getDate();
|
const { dayNumber, monthName, year, weekdayName } = getDayMonthYear(day, weekdays, monthsLabels);
|
||||||
const monthName = monthsLabels[day.date.getMonth()];
|
|
||||||
const year = day.date.getFullYear();
|
function __getFullDate() {
|
||||||
const weekdayName = day.weekOrder ? weekdays[day.weekOrder] : weekdays[0];
|
return `${monthName} ${year} ${weekdayName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __getAccessibleMessage() {
|
||||||
|
return `${day.disabledInfo}`;
|
||||||
|
}
|
||||||
|
|
||||||
const firstDay = dayNumber === 1;
|
const firstDay = dayNumber === 1;
|
||||||
const endOfFirstWeek = day.weekOrder === 6 && firstWeekDays.includes(dayNumber);
|
const endOfFirstWeek = day.weekOrder === 6 && firstWeekDays.includes(dayNumber);
|
||||||
|
|
@ -57,20 +49,14 @@ export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }
|
||||||
?start-of-last-week=${startOfLastWeek}
|
?start-of-last-week=${startOfLastWeek}
|
||||||
?last-day=${lastDay}
|
?last-day=${lastDay}
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
|
role="button"
|
||||||
.date=${day.date}
|
.date=${day.date}
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex=${ifDefined(Number(day.tabindex))}
|
tabindex=${ifDefined(day.tabindex)}
|
||||||
aria-label=${`${dayNumber} ${monthName} ${year} ${weekdayName}`}
|
aria-pressed=${ifDefined(day.ariaPressed)}
|
||||||
aria-pressed=${
|
aria-current=${ifDefined(day.ariaCurrent)}
|
||||||
/** @type {'true'|'false'|'mixed'|'undefined'} */ (ifDefined(day.ariaPressed))
|
aria-disabled=${day.disabled ? 'true' : 'false'}
|
||||||
}
|
|
||||||
aria-current=${
|
|
||||||
/** @type {'page'|'step'|'location'|'date'|'time'|'true'|'false'} */ (
|
|
||||||
ifDefined(day.ariaCurrent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
?disabled=${day.disabled}
|
|
||||||
?selected=${day.selected}
|
?selected=${day.selected}
|
||||||
?past=${day.past}
|
?past=${day.past}
|
||||||
?today=${day.today}
|
?today=${day.today}
|
||||||
|
|
@ -79,8 +65,9 @@ export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }
|
||||||
?current-month=${day.currentMonth}
|
?current-month=${day.currentMonth}
|
||||||
?next-month=${day.nextMonth}
|
?next-month=${day.nextMonth}
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text"> ${day.date.getDate()} </span>
|
<span class="calendar__day-button__text">${dayNumber}</span>
|
||||||
</button>
|
<span class="u-sr-only">${__getFullDate()} ${__getAccessibleMessage()}</span>
|
||||||
|
</div>
|
||||||
</td>
|
</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() {
|
get isDisabled() {
|
||||||
return this.buttonEl.hasAttribute('disabled');
|
return this.buttonEl.getAttribute('aria-disabled') === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
get isSelected() {
|
get isSelected() {
|
||||||
|
|
@ -54,7 +54,7 @@ export class DayObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
get monthday() {
|
get monthday() {
|
||||||
return Number(this.buttonEl.textContent);
|
return Number(this.buttonEl.children[0].textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ describe('<lion-calendar>', () => {
|
||||||
clock.restore();
|
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 clock = sinon.useFakeTimers({ now: new Date('2019/06/03').getTime() });
|
||||||
|
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
|
|
@ -405,9 +405,18 @@ describe('<lion-calendar>', () => {
|
||||||
.maxDate="${new Date('2019/07/03')}"
|
.maxDate="${new Date('2019/07/03')}"
|
||||||
></lion-calendar>
|
></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();
|
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', () => {
|
describe('Normalization', () => {
|
||||||
|
|
@ -482,6 +491,98 @@ describe('<lion-calendar>', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Navigation', () => {
|
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', () => {
|
describe('Year', () => {
|
||||||
it('has a button for navigation to previous year', async () => {
|
it('has a button for navigation to previous year', async () => {
|
||||||
const el = await fixture(
|
const el = await fixture(
|
||||||
|
|
@ -655,7 +756,7 @@ describe('<lion-calendar>', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(elObj.activeMonth).to.equal('November');
|
expect(elObj.activeMonth).to.equal('November');
|
||||||
expect(elObj.activeYear).to.equal('2000');
|
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();
|
clock.restore();
|
||||||
});
|
});
|
||||||
|
|
@ -677,7 +778,7 @@ describe('<lion-calendar>', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(elObj.activeMonth).to.equal('January');
|
expect(elObj.activeMonth).to.equal('January');
|
||||||
expect(elObj.activeYear).to.equal('2001');
|
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();
|
clock.restore();
|
||||||
});
|
});
|
||||||
|
|
@ -696,17 +797,21 @@ describe('<lion-calendar>', () => {
|
||||||
expect(remote.activeMonth).to.equal('September');
|
expect(remote.activeMonth).to.equal('September');
|
||||||
expect(remote.activeYear).to.equal('2019');
|
expect(remote.activeYear).to.equal('2019');
|
||||||
expect(remote.centralDayObj?.el).dom.to.equal(`
|
expect(remote.centralDayObj?.el).dom.to.equal(`
|
||||||
<button
|
<div
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
|
aria-disabled="false"
|
||||||
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-label="30 September 2019 Monday"
|
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
past=""
|
past=""
|
||||||
current-month="">
|
current-month="">
|
||||||
<span class="calendar__day-button__text">
|
<span class="calendar__day-button__text">
|
||||||
30
|
30
|
||||||
</span>
|
</span>
|
||||||
</button>
|
<span class="u-sr-only">
|
||||||
|
September 2019 Monday
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -801,7 +906,7 @@ describe('<lion-calendar>', () => {
|
||||||
).to.equal(true);
|
).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 clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||||
|
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
|
|
@ -815,7 +920,7 @@ describe('<lion-calendar>', () => {
|
||||||
const elObj = new CalendarObject(el);
|
const elObj = new CalendarObject(el);
|
||||||
expect(
|
expect(
|
||||||
elObj.checkForAllDayObjs(
|
elObj.checkForAllDayObjs(
|
||||||
/** @param {DayObject} d */ d => d.el.hasAttribute('disabled'),
|
/** @param {DayObject} d */ d => d.el.getAttribute('aria-disabled') === 'true',
|
||||||
[1, 2, 30, 31],
|
[1, 2, 30, 31],
|
||||||
),
|
),
|
||||||
).to.equal(true);
|
).to.equal(true);
|
||||||
|
|
@ -972,7 +1077,7 @@ describe('<lion-calendar>', () => {
|
||||||
expect(elObj.focusedDayObj?.monthday).to.equal(12 + 1);
|
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`
|
const el = await fixture(html`
|
||||||
<lion-calendar
|
<lion-calendar
|
||||||
.selectedDate="${new Date('2001/01/02')}"
|
.selectedDate="${new Date('2001/01/02')}"
|
||||||
|
|
@ -987,7 +1092,7 @@ describe('<lion-calendar>', () => {
|
||||||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||||
);
|
);
|
||||||
await el.updateComplete;
|
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 () => {
|
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();
|
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 () => {
|
it('renders each day as a button inside a table cell', async () => {
|
||||||
const elObj = new CalendarObject(await fixture(html`<lion-calendar></lion-calendar>`));
|
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);
|
expect(elObj.checkForAllDayObjs(hasBtn)).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1301,31 +1336,6 @@ describe('<lion-calendar>', () => {
|
||||||
expect(elObj.checkForAllDayObjs(hasAriaPressed, [12])).to.equal(true);
|
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:
|
* Not in scope:
|
||||||
* - reads the new focused day on month navigation"
|
* - reads the new focused day on month navigation"
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,18 @@ describe('dayTemplate', () => {
|
||||||
const el = await fixture(dayTemplate(day, { weekdays }));
|
const el = await fixture(dayTemplate(day, { weekdays }));
|
||||||
expect(el).dom.to.equal(`
|
expect(el).dom.to.equal(`
|
||||||
<td role="gridcell" class="calendar__day-cell">
|
<td role="gridcell" class="calendar__day-cell">
|
||||||
<button
|
<div
|
||||||
class="calendar__day-button"
|
aria-disabled="false"
|
||||||
aria-label="19 April 2019 Friday"
|
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
|
role="button"
|
||||||
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">19</span>
|
<span class="calendar__day-button__text">19</span>
|
||||||
</button>
|
<span class="u-sr-only">
|
||||||
|
April 2019 Friday
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -52,434 +52,518 @@ export default html`
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<td class="calendar__day-cell" role="gridcell" start-of-last-week>
|
<td class="calendar__day-cell" role="gridcell" start-of-last-week>
|
||||||
<button
|
<div
|
||||||
aria-label="25 November 2018 Sunday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">25</span>
|
<span class="calendar__day-button__text">25</span>
|
||||||
</button>
|
<span class="u-sr-only"> November 2018 Sunday</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="26 November 2018 Monday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">26</span>
|
<span class="calendar__day-button__text">26</span>
|
||||||
</button>
|
<span class="u-sr-only"> November 2018 Monday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="27 November 2018 Tuesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">27</span>
|
<span class="calendar__day-button__text">27</span>
|
||||||
</button>
|
<span class="u-sr-only"> November 2018 Tuesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="28 November 2018 Wednesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">28</span>
|
<span class="calendar__day-button__text">28</span>
|
||||||
</button>
|
<span class="u-sr-only"> November 2018 Wednesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="29 November 2018 Thursday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">29</span>
|
<span class="calendar__day-button__text">29</span>
|
||||||
</button>
|
<span class="u-sr-only"> November 2018 Thursday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell" last-day>
|
<td class="calendar__day-cell" role="gridcell" last-day>
|
||||||
<button
|
<div
|
||||||
aria-label="30 November 2018 Friday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">30</span>
|
<span class="calendar__day-button__text">30</span>
|
||||||
</button>
|
<span class="u-sr-only"> November 2018 Friday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell" end-of-first-week first-day>
|
<td class="calendar__day-cell" role="gridcell" end-of-first-week first-day>
|
||||||
<button
|
<div
|
||||||
aria-label="1 December 2018 Saturday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">1</span>
|
<span class="calendar__day-button__text">1</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<td class="calendar__day-cell" role="gridcell" start-of-first-full-week>
|
<td class="calendar__day-cell" role="gridcell" start-of-first-full-week>
|
||||||
<button
|
<div
|
||||||
aria-label="2 December 2018 Sunday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">2</span>
|
<span class="calendar__day-button__text">2</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="3 December 2018 Monday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">3</span>
|
<span class="calendar__day-button__text">3</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Monday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="4 December 2018 Tuesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">4</span>
|
<span class="calendar__day-button__text">4</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="5 December 2018 Wednesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">5</span>
|
<span class="calendar__day-button__text">5</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="6 December 2018 Thursday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">6</span>
|
<span class="calendar__day-button__text">6</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="7 December 2018 Friday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">7</span>
|
<span class="calendar__day-button__text">7</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Friday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="8 December 2018 Saturday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">8</span>
|
<span class="calendar__day-button__text">8</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="9 December 2018 Sunday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">9</span>
|
<span class="calendar__day-button__text">9</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="10 December 2018 Monday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">10</span>
|
<span class="calendar__day-button__text">10</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Monday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="11 December 2018 Tuesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">11</span>
|
<span class="calendar__day-button__text">11</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="12 December 2018 Wednesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">12</span>
|
<span class="calendar__day-button__text">12</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="13 December 2018 Thursday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">13</span>
|
<span class="calendar__day-button__text">13</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="14 December 2018 Friday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">14</span>
|
<span class="calendar__day-button__text">14</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Friday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="15 December 2018 Saturday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">15</span>
|
<span class="calendar__day-button__text">15</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="16 December 2018 Sunday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">16</span>
|
<span class="calendar__day-button__text">16</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="17 December 2018 Monday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">17</span>
|
<span class="calendar__day-button__text">17</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Monday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="18 December 2018 Tuesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">18</span>
|
<span class="calendar__day-button__text">18</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="19 December 2018 Wednesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">19</span>
|
<span class="calendar__day-button__text">19</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="20 December 2018 Thursday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">20</span>
|
<span class="calendar__day-button__text">20</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="21 December 2018 Friday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">21</span>
|
<span class="calendar__day-button__text">21</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Friday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="22 December 2018 Saturday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">22</span>
|
<span class="calendar__day-button__text">22</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="23 December 2018 Sunday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">23</span>
|
<span class="calendar__day-button__text">23</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="24 December 2018 Monday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">24</span>
|
<span class="calendar__day-button__text">24</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Monday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="25 December 2018 Tuesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">25</span>
|
<span class="calendar__day-button__text">25</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Tuesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="26 December 2018 Wednesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">26</span>
|
<span class="calendar__day-button__text">26</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Wednesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="27 December 2018 Thursday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">27</span>
|
<span class="calendar__day-button__text">27</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Thursday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="28 December 2018 Friday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">28</span>
|
<span class="calendar__day-button__text">28</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Friday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell" end-of-last-full-week>
|
<td class="calendar__day-cell" role="gridcell" end-of-last-full-week>
|
||||||
<button
|
<div
|
||||||
aria-label="29 December 2018 Saturday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">29</span>
|
<span class="calendar__day-button__text">29</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Saturday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<td class="calendar__day-cell" role="gridcell" start-of-last-week>
|
<td class="calendar__day-cell" role="gridcell" start-of-last-week>
|
||||||
<button
|
<div
|
||||||
aria-label="30 December 2018 Sunday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">30</span>
|
<span class="calendar__day-button__text">30</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Sunday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" last-day role="gridcell">
|
<td class="calendar__day-cell" last-day role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="31 December 2018 Monday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">31</span>
|
<span class="calendar__day-button__text">31</span>
|
||||||
</button>
|
<span class="u-sr-only"> December 2018 Monday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell" first-day>
|
<td class="calendar__day-cell" role="gridcell" first-day>
|
||||||
<button
|
<div
|
||||||
aria-label="1 January 2019 Tuesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">1</span>
|
<span class="calendar__day-button__text">1</span>
|
||||||
</button>
|
<span class="u-sr-only"> January 2019 Tuesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="2 January 2019 Wednesday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">2</span>
|
<span class="calendar__day-button__text">2</span>
|
||||||
</button>
|
<span class="u-sr-only"> January 2019 Wednesday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="3 January 2019 Thursday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">3</span>
|
<span class="calendar__day-button__text">3</span>
|
||||||
</button>
|
<span class="u-sr-only"> January 2019 Thursday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell">
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
<button
|
<div
|
||||||
aria-label="4 January 2019 Friday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">4</span>
|
<span class="calendar__day-button__text">4</span>
|
||||||
</button>
|
<span class="u-sr-only"> January 2019 Friday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="calendar__day-cell" role="gridcell" end-of-first-week>
|
<td class="calendar__day-cell" role="gridcell" end-of-first-week>
|
||||||
<button
|
<div
|
||||||
aria-label="5 January 2019 Saturday"
|
role="button"
|
||||||
|
aria-disabled="false"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="calendar__day-button"
|
class="calendar__day-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="calendar__day-button__text">5</span>
|
<span class="calendar__day-button__text">5</span>
|
||||||
</button>
|
<span class="u-sr-only"> January 2019 Saturday </span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export declare interface Day {
|
||||||
tabindex?: string;
|
tabindex?: string;
|
||||||
ariaPressed?: string;
|
ariaPressed?: string;
|
||||||
ariaCurrent?: string | undefined;
|
ariaCurrent?: string | undefined;
|
||||||
|
disabledInfo?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface Week {
|
export declare interface Week {
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,28 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
||||||
|
|
||||||
__calendarDisableDates: {
|
__calendarDisableDates: {
|
||||||
attribute: false,
|
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() {
|
get slots() {
|
||||||
return {
|
return {
|
||||||
...super.slots,
|
...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
|
* @param {{ target: { selectedDate: Date }}} opts
|
||||||
*/
|
*/
|
||||||
_onCalendarUserSelectedChanged({ target: { selectedDate } }) {
|
_onCalendarUserSelectedChanged({ target: { selectedDate } }) {
|
||||||
|
|
@ -355,8 +374,21 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
||||||
if (this._syncOnUserSelect) {
|
if (this._syncOnUserSelect) {
|
||||||
// Synchronize new selectedDate value to input
|
// Synchronize new selectedDate value to input
|
||||||
this._isHandlingUserInput = true;
|
this._isHandlingUserInput = true;
|
||||||
this._isHandlingCalendarUserInput = true;
|
|
||||||
this.modelValue = selectedDate;
|
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._isHandlingUserInput = false;
|
||||||
this._isHandlingCalendarUserInput = false;
|
this._isHandlingCalendarUserInput = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue