feat: add types for calendar and datepicker
This commit is contained in:
parent
580603cedf
commit
e9cee0397b
26 changed files with 615 additions and 309 deletions
6
.changeset/hip-pears-vanish.md
Normal file
6
.changeset/hip-pears-vanish.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@lion/calendar': minor
|
||||
'@lion/input-datepicker': minor
|
||||
---
|
||||
|
||||
Add types for calendar and datepicker packages.
|
||||
|
|
@ -16,14 +16,21 @@ import { getFirstDayNextMonth } from './utils/getFirstDayNextMonth.js';
|
|||
import { getLastDayPreviousMonth } from './utils/getLastDayPreviousMonth.js';
|
||||
import { isSameDate } from './utils/isSameDate.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/day').Day} Day
|
||||
* @typedef {import('../types/day').Week} Week
|
||||
* @typedef {import('../types/day').Month} Month
|
||||
*/
|
||||
|
||||
/**
|
||||
* @customElement lion-calendar
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||
static get localizeNamespaces() {
|
||||
return [
|
||||
{
|
||||
'lion-calendar': locale => {
|
||||
'lion-calendar': /** @param {string} locale */ locale => {
|
||||
switch (locale) {
|
||||
case 'bg-BG':
|
||||
return import('../translations/bg.js');
|
||||
|
|
@ -75,37 +82,37 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
/**
|
||||
* Minimum date. All dates before will be disabled
|
||||
*/
|
||||
minDate: { type: Date },
|
||||
minDate: { attribute: false },
|
||||
|
||||
/**
|
||||
* Maximum date. All dates after will be disabled
|
||||
*/
|
||||
maxDate: { type: Date },
|
||||
maxDate: { attribute: false },
|
||||
|
||||
/**
|
||||
* Disable certain dates
|
||||
*/
|
||||
disableDates: { type: Function },
|
||||
disableDates: { attribute: false },
|
||||
|
||||
/**
|
||||
* The selected date, usually synchronized with datepicker-input
|
||||
* Not to be confused with the focused date (therefore not necessarily in active month view)
|
||||
*/
|
||||
selectedDate: { type: Date },
|
||||
selectedDate: { attribute: false },
|
||||
|
||||
/**
|
||||
* The date that
|
||||
* 1. determines the currently visible month
|
||||
* 2. will be focused when the month grid gets focused by the keyboard
|
||||
*/
|
||||
centralDate: { type: Date },
|
||||
centralDate: { attribute: false },
|
||||
|
||||
/**
|
||||
* Weekday that will be displayed in first column of month grid.
|
||||
* 0: sunday, 1: monday, 2: tuesday, 3: wednesday , 4: thursday, 5: friday, 6: saturday
|
||||
* Default is 0
|
||||
*/
|
||||
firstDayOfWeek: { type: Number },
|
||||
firstDayOfWeek: { attribute: false },
|
||||
|
||||
/**
|
||||
* Weekday header notation, based on Intl DatetimeFormat:
|
||||
|
|
@ -114,39 +121,48 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* - 'narrow' (e.g., T).
|
||||
* Default is 'short'
|
||||
*/
|
||||
weekdayHeaderNotation: { type: String },
|
||||
weekdayHeaderNotation: { attribute: false },
|
||||
|
||||
/**
|
||||
* Different locale for this component scope
|
||||
*/
|
||||
locale: { type: String },
|
||||
locale: { attribute: false },
|
||||
|
||||
/**
|
||||
* The currently focused date (if any)
|
||||
*/
|
||||
__focusedDate: { type: Date },
|
||||
__focusedDate: { attribute: false },
|
||||
|
||||
/**
|
||||
* Data to render current month grid
|
||||
*/
|
||||
__data: { type: Object },
|
||||
__data: { attribute: false },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// Defaults
|
||||
this.__data = {};
|
||||
this.minDate = null;
|
||||
this.maxDate = null;
|
||||
/** @type {{months: Month[]}} */
|
||||
this.__data = { months: [] };
|
||||
this.minDate = new Date(0);
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
|
||||
this.maxDate = new Date(8640000000000000);
|
||||
/** @param {Day} day */
|
||||
this.dayPreprocessor = day => day;
|
||||
this.disableDates = () => false;
|
||||
|
||||
/** @param {Date} day */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this.disableDates = day => false;
|
||||
|
||||
this.firstDayOfWeek = 0;
|
||||
this.weekdayHeaderNotation = 'short';
|
||||
this.__today = normalizeDateTime(new Date());
|
||||
/** @type {Date} */
|
||||
this.centralDate = this.__today;
|
||||
/** @type {Date | null} */
|
||||
this.__focusedDate = null;
|
||||
this.__connectedCallbackDone = false;
|
||||
this.locale = '';
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
|
@ -181,6 +197,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
this.__modifyDate(-1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
*/
|
||||
async focusDate(date) {
|
||||
this.centralDate = date;
|
||||
await this.updateComplete;
|
||||
|
|
@ -188,16 +207,20 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
focusCentralDate() {
|
||||
const button = this.shadowRoot.querySelector('button[tabindex="0"]');
|
||||
const button = /** @type {HTMLElement} */ (this.shadowRoot?.querySelector(
|
||||
'button[tabindex="0"]',
|
||||
));
|
||||
button.focus();
|
||||
this.__focusedDate = this.centralDate;
|
||||
}
|
||||
|
||||
async focusSelectedDate() {
|
||||
await this.focusDate(this.selectedDate);
|
||||
if (this.selectedDate) {
|
||||
await this.focusDate(this.selectedDate);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
async connectedCallback() {
|
||||
// eslint-disable-next-line wc/guard-super-call
|
||||
super.connectedCallback();
|
||||
|
||||
|
|
@ -207,32 +230,36 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
|
||||
// setup data for initial render
|
||||
this.__data = this.__createData();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (super.disconnectedCallback) {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
this.__removeEventDelegations();
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
super.firstUpdated();
|
||||
this.__contentWrapperElement = this.shadowRoot.getElementById('js-content-wrapper');
|
||||
/**
|
||||
* This logic needs to happen on firstUpdated, but every time the DOM node is moved as well
|
||||
* since firstUpdated only runs once, this logic is moved here, but after updateComplete
|
||||
* this acts as a firstUpdated that runs on every reconnect as well
|
||||
*/
|
||||
await this.updateComplete;
|
||||
this.__contentWrapperElement = this.shadowRoot?.getElementById('js-content-wrapper');
|
||||
this.__addEventDelegationForClickDate();
|
||||
this.__addEventDelegationForFocusDate();
|
||||
this.__addEventDelegationForBlurDate();
|
||||
this.__addEventForKeyboardNavigation();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.__removeEventDelegations();
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('__focusedDate') && this.__focusedDate) {
|
||||
this.focusCentralDate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {string} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
|
|
@ -262,6 +289,10 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} month
|
||||
* @param {number} year
|
||||
*/
|
||||
__renderMonthNavigation(month, year) {
|
||||
const nextMonth =
|
||||
this.centralDate.getMonth() === 11
|
||||
|
|
@ -282,6 +313,10 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} month
|
||||
* @param {number} year
|
||||
*/
|
||||
__renderYearNavigation(month, year) {
|
||||
const nextYear = year + 1;
|
||||
const previousYear = year - 1;
|
||||
|
|
@ -322,6 +357,11 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {string} previousMonth
|
||||
* @param {number} previousYear
|
||||
*/
|
||||
__getPreviousDisabled(type, previousMonth, previousYear) {
|
||||
let disabled;
|
||||
let month = previousMonth;
|
||||
|
|
@ -341,6 +381,11 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
return { disabled, month };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {string} nextMonth
|
||||
* @param {number} nextYear
|
||||
*/
|
||||
__getNextDisabled(type, nextMonth, nextYear) {
|
||||
let disabled;
|
||||
let month = nextMonth;
|
||||
|
|
@ -360,16 +405,21 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
return { disabled, month };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {string} previousMonth
|
||||
* @param {number} previousYear
|
||||
*/
|
||||
__renderPreviousButton(type, previousMonth, previousYear) {
|
||||
const { disabled, month } = this.__getPreviousDisabled(type, previousMonth, previousYear);
|
||||
const previousButtonTitle = this.__getNavigationLabel('previous', type, month, previousYear);
|
||||
function clickDateDelegation() {
|
||||
const clickDateDelegation = () => {
|
||||
if (type === 'FullYear') {
|
||||
this.goToPreviousYear();
|
||||
} else {
|
||||
this.goToPreviousMonth();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return html`
|
||||
<button
|
||||
|
|
@ -384,16 +434,21 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {string} nextMonth
|
||||
* @param {number} nextYear
|
||||
*/
|
||||
__renderNextButton(type, nextMonth, nextYear) {
|
||||
const { disabled, month } = this.__getNextDisabled(type, nextMonth, nextYear);
|
||||
const nextButtonTitle = this.__getNavigationLabel('next', type, month, nextYear);
|
||||
function clickDateDelegation() {
|
||||
const clickDateDelegation = () => {
|
||||
if (type === 'FullYear') {
|
||||
this.goToNextYear();
|
||||
} else {
|
||||
this.goToNextMonth();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return html`
|
||||
<button
|
||||
|
|
@ -408,10 +463,22 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} mode
|
||||
* @param {string} type
|
||||
* @param {string} month
|
||||
* @param {number} year
|
||||
*/
|
||||
__getNavigationLabel(mode, type, month, year) {
|
||||
return `${this.msgLit(`lion-calendar:${mode}${type}`)}, ${month} ${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Day} _day
|
||||
* @param {*} param1
|
||||
*/
|
||||
__coreDayPreprocessor(_day, { currentMonth = false } = {}) {
|
||||
const day = createDay(new Date(_day.date), _day);
|
||||
const today = normalizeDateTime(new Date());
|
||||
|
|
@ -439,6 +506,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
return this.dayPreprocessor(day);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Day} [options]
|
||||
*/
|
||||
__createData(options) {
|
||||
const data = createMultipleMonth(this.centralDate, {
|
||||
firstDayOfWeek: this.firstDayOfWeek,
|
||||
|
|
@ -465,6 +535,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} selectedDate
|
||||
*/
|
||||
__dateSelectedByUser(selectedDate) {
|
||||
this.selectedDate = selectedDate;
|
||||
this.__focusedDate = selectedDate;
|
||||
|
|
@ -495,6 +568,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
*/
|
||||
__isEnabledDate(date) {
|
||||
const processedDay = this.__coreDayPreprocessor({ date });
|
||||
return !processedDay.disabled;
|
||||
|
|
@ -543,55 +619,81 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
__addEventDelegationForClickDate() {
|
||||
const isDayButton = el => el.classList.contains('calendar__day-button');
|
||||
this.__clickDateDelegation = this.__contentWrapperElement.addEventListener('click', ev => {
|
||||
const el = ev.target;
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
this.__clickDateDelegation = /** @param {Event} ev */ ev => {
|
||||
const el = /** @type {HTMLElement & { date: Date }} */ (ev.target);
|
||||
if (isDayButton(el)) {
|
||||
this.__dateSelectedByUser(el.date);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const contentWrapper = /** @type {HTMLButtonElement} */ (this.__contentWrapperElement);
|
||||
contentWrapper.addEventListener('click', this.__clickDateDelegation);
|
||||
}
|
||||
|
||||
__addEventDelegationForFocusDate() {
|
||||
const isDayButton = el => el.classList.contains('calendar__day-button');
|
||||
this.__focusDateDelegation = this.__contentWrapperElement.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
if (!this.__focusedDate && isDayButton(this.shadowRoot.activeElement)) {
|
||||
this.__focusedDate = this.shadowRoot.activeElement.date;
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
this.__focusDateDelegation = () => {
|
||||
if (
|
||||
!this.__focusedDate &&
|
||||
isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
|
||||
) {
|
||||
this.__focusedDate = /** @type {HTMLButtonElement & { date: Date }} */ (this.shadowRoot
|
||||
?.activeElement).date;
|
||||
}
|
||||
};
|
||||
|
||||
const contentWrapper = /** @type {HTMLButtonElement} */ (this.__contentWrapperElement);
|
||||
contentWrapper.addEventListener('focus', this.__focusDateDelegation, true);
|
||||
}
|
||||
|
||||
__addEventDelegationForBlurDate() {
|
||||
const isDayButton = el => el.classList.contains('calendar__day-button');
|
||||
this.__blurDateDelegation = this.__contentWrapperElement.addEventListener(
|
||||
'blur',
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
if (this.shadowRoot.activeElement && !isDayButton(this.shadowRoot.activeElement)) {
|
||||
this.__focusedDate = null;
|
||||
}
|
||||
}, 1);
|
||||
},
|
||||
true,
|
||||
);
|
||||
const isDayButton = /** @param {HTMLElement} el */ el =>
|
||||
el.classList.contains('calendar__day-button');
|
||||
|
||||
this.__blurDateDelegation = () => {
|
||||
setTimeout(() => {
|
||||
if (
|
||||
this.shadowRoot?.activeElement &&
|
||||
!isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
|
||||
) {
|
||||
this.__focusedDate = null;
|
||||
}
|
||||
}, 1);
|
||||
};
|
||||
|
||||
const contentWrapper = /** @type {HTMLButtonElement} */ (this.__contentWrapperElement);
|
||||
contentWrapper.addEventListener('blur', this.__blurDateDelegation, true);
|
||||
}
|
||||
|
||||
__removeEventDelegations() {
|
||||
if (!this.__contentWrapperElement) {
|
||||
return;
|
||||
}
|
||||
this.__contentWrapperElement.removeEventListener('click', this.__clickDateDelegation);
|
||||
this.__contentWrapperElement.removeEventListener('focus', this.__focusDateDelegation);
|
||||
this.__contentWrapperElement.removeEventListener('blur', this.__blurDateDelegation);
|
||||
this.__contentWrapperElement.removeEventListener('keydown', this.__keyNavigationEvent);
|
||||
this.__contentWrapperElement.removeEventListener(
|
||||
'click',
|
||||
/** @type {EventListener} */ (this.__clickDateDelegation),
|
||||
);
|
||||
this.__contentWrapperElement.removeEventListener(
|
||||
'focus',
|
||||
/** @type {EventListener} */ (this.__focusDateDelegation),
|
||||
);
|
||||
this.__contentWrapperElement.removeEventListener(
|
||||
'blur',
|
||||
/** @type {EventListener} */ (this.__blurDateDelegation),
|
||||
);
|
||||
this.__contentWrapperElement.removeEventListener(
|
||||
'keydown',
|
||||
/** @type {EventListener} */ (this.__keyNavigationEvent),
|
||||
);
|
||||
}
|
||||
|
||||
__addEventForKeyboardNavigation() {
|
||||
this.__keyNavigationEvent = this.__contentWrapperElement.addEventListener('keydown', ev => {
|
||||
this.__keyNavigationEvent = /** @param {KeyboardEvent} ev */ ev => {
|
||||
const preventedKeys = ['ArrowUp', 'ArrowDown', 'PageDown', 'PageUp'];
|
||||
|
||||
if (preventedKeys.includes(ev.key)) {
|
||||
|
|
@ -630,10 +732,21 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
break;
|
||||
// no default
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const contentWrapper = /** @type {HTMLButtonElement} */ (this.__contentWrapperElement);
|
||||
contentWrapper.addEventListener('keydown', this.__keyNavigationEvent);
|
||||
}
|
||||
|
||||
__modifyDate(modify, { dateType, type, mode } = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {number} modify
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.dateType
|
||||
* @param {string} opts.type
|
||||
* @param {string} opts.mode
|
||||
*/
|
||||
__modifyDate(modify, { dateType, type, mode }) {
|
||||
let tmpDate = new Date(this.centralDate);
|
||||
// if we're not working with days, reset
|
||||
// day count to first day of the month
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
/**
|
||||
* @param {Date} date,
|
||||
* @returns {import('../../types/day').Day} day
|
||||
*/
|
||||
export function createDay(
|
||||
date = new Date(),
|
||||
{
|
||||
weekOrder,
|
||||
weekOrder = 0,
|
||||
central = false,
|
||||
startOfWeek = false,
|
||||
selected = false,
|
||||
|
|
@ -11,6 +15,7 @@ export function createDay(
|
|||
past = false,
|
||||
today = false,
|
||||
future = false,
|
||||
disabled = false,
|
||||
} = {},
|
||||
) {
|
||||
return {
|
||||
|
|
@ -25,6 +30,7 @@ export function createDay(
|
|||
past,
|
||||
today,
|
||||
future,
|
||||
disabled,
|
||||
tabindex: '-1',
|
||||
ariaPressed: 'false',
|
||||
ariaCurrent: undefined,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import { createWeek } from './createWeek.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Date} date
|
||||
* @param {Object} opts
|
||||
* @param {number} [opts.firstDayOfWeek]
|
||||
* @returns {import('../../types/day').Month}
|
||||
*/
|
||||
export function createMonth(date, { firstDayOfWeek = 0 } = {}) {
|
||||
if (Object.prototype.toString.call(date) !== '[object Date]') {
|
||||
throw new Error('invalid date provided');
|
||||
|
|
@ -10,6 +17,7 @@ export function createMonth(date, { firstDayOfWeek = 0 } = {}) {
|
|||
const weekOptions = { firstDayOfWeek };
|
||||
|
||||
const month = {
|
||||
/** @type {{days: import('../../types/day').Day[]}[]} */
|
||||
weeks: [],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import { createMonth } from './createMonth.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Date} date
|
||||
* @return {{months: import('../../types/day').Month[]}}
|
||||
*/
|
||||
export function createMultipleMonth(
|
||||
date,
|
||||
{ firstDayOfWeek = 0, pastMonths = 0, futureMonths = 0 } = {},
|
||||
) {
|
||||
const multipleMonths = {
|
||||
/** @type {{weeks: {days: import('../../types/day').Day[]}[]}[]} */
|
||||
months: [],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import { createDay } from './createDay.js';
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @param {Object} opts
|
||||
* @param {number} [opts.firstDayOfWeek]
|
||||
* @returns {import('../../types/day').Week}
|
||||
*/
|
||||
export function createWeek(date, { firstDayOfWeek = 0 } = {}) {
|
||||
if (Object.prototype.toString.call(date) !== '[object Date]') {
|
||||
throw new Error('invalid date provided');
|
||||
|
|
@ -13,6 +19,7 @@ export function createWeek(date, { firstDayOfWeek = 0 } = {}) {
|
|||
}
|
||||
|
||||
const week = {
|
||||
/** @type {import('../../types/day').Day[]} */
|
||||
days: [],
|
||||
};
|
||||
for (let i = 0; i < 7; i += 1) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { html } from '@lion/core';
|
||||
import { dayTemplate as defaultDayTemplate } from './dayTemplate.js';
|
||||
|
||||
/**
|
||||
* @param {{months: {weeks: {days: import('../../types/day').Day[]}[]}[]}} data
|
||||
* @param {{ weekdaysShort: string[], weekdays: string[], monthsLabels?: string[], dayTemplate?: (day: import('../../types/day').Day, { weekdays, monthsLabels }?: any) => import('@lion/core').TemplateResult }} opts
|
||||
*/
|
||||
export function dataTemplate(
|
||||
data,
|
||||
{ weekdaysShort, weekdays, monthsLabels, dayTemplate = defaultDayTemplate } = {},
|
||||
{ weekdaysShort, weekdays, monthsLabels, dayTemplate = defaultDayTemplate },
|
||||
) {
|
||||
return html`
|
||||
<div id="js-content-wrapper">
|
||||
|
|
|
|||
|
|
@ -17,11 +17,16 @@ const defaultMonthLabels = [
|
|||
const firstWeekDays = [1, 2, 3, 4, 5, 6, 7];
|
||||
const lastDaysOfYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels } = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {import('../../types/day').Day} day
|
||||
* @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 = weekdays[day.weekOrder];
|
||||
const weekdayName = day.weekOrder ? weekdays[day.weekOrder] : weekdays[0];
|
||||
|
||||
const firstDay = dayNumber === 1;
|
||||
const endOfFirstWeek = day.weekOrder === 6 && firstWeekDays.includes(dayNumber);
|
||||
|
|
@ -54,9 +59,9 @@ export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }
|
|||
<button
|
||||
.date=${day.date}
|
||||
class="calendar__day-button"
|
||||
tabindex=${day.tabindex}
|
||||
tabindex=${ifDefined(day.tabindex)}
|
||||
aria-label=${`${dayNumber} ${monthName} ${year} ${weekdayName}`}
|
||||
aria-pressed=${day.ariaPressed}
|
||||
aria-pressed=${ifDefined(day.ariaPressed)}
|
||||
aria-current=${ifDefined(day.ariaCurrent)}
|
||||
?disabled=${day.disabled}
|
||||
?selected=${day.selected}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* @param {Date} date
|
||||
*
|
||||
* returns {Date}
|
||||
* @returns {Date}
|
||||
*/
|
||||
export function getFirstDayNextMonth(date) {
|
||||
const result = new Date(date);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* @param {Date} date
|
||||
*
|
||||
* returns {Date}
|
||||
* @returns {Date}
|
||||
*/
|
||||
export function getLastDayPreviousMonth(date) {
|
||||
const previous = new Date(date);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* @param {Date} day1
|
||||
* @param {Date} day2
|
||||
*
|
||||
* returns {boolean}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isSameDate(day1, day2) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { DayObject } from './DayObject.js';
|
|||
* allows for writing readable, 'DOM structure agnostic' tests
|
||||
*/
|
||||
export class CalendarObject {
|
||||
/**
|
||||
* @param {import('../src/LionCalendar').LionCalendar} calendarEl
|
||||
*/
|
||||
constructor(calendarEl) {
|
||||
this.el = calendarEl;
|
||||
}
|
||||
|
|
@ -14,59 +17,73 @@ export class CalendarObject {
|
|||
*/
|
||||
|
||||
get rootEl() {
|
||||
return this.el.shadowRoot.querySelector('.calendar');
|
||||
return this.el.shadowRoot?.querySelector('.calendar');
|
||||
}
|
||||
|
||||
get headerEl() {
|
||||
return this.el.shadowRoot.querySelector('.calendar__navigation');
|
||||
return this.el.shadowRoot?.querySelector('.calendar__navigation');
|
||||
}
|
||||
|
||||
get yearHeadingEl() {
|
||||
return this.el.shadowRoot.querySelector('#year');
|
||||
return this.el.shadowRoot?.querySelector('#year');
|
||||
}
|
||||
|
||||
get monthHeadingEl() {
|
||||
return this.el.shadowRoot.querySelector('#month');
|
||||
return this.el.shadowRoot?.querySelector('#month');
|
||||
}
|
||||
|
||||
get nextYearButtonEl() {
|
||||
return this.el.shadowRoot.querySelectorAll('.calendar__next-button')[0];
|
||||
return /** @type {HTMLElement & { ariaLabel: string }} */ (this.el.shadowRoot?.querySelectorAll(
|
||||
'.calendar__next-button',
|
||||
)[0]);
|
||||
}
|
||||
|
||||
get previousYearButtonEl() {
|
||||
return this.el.shadowRoot.querySelectorAll('.calendar__previous-button')[0];
|
||||
return /** @type {HTMLElement & { ariaLabel: string }} */ (this.el.shadowRoot?.querySelectorAll(
|
||||
'.calendar__previous-button',
|
||||
)[0]);
|
||||
}
|
||||
|
||||
get nextMonthButtonEl() {
|
||||
return this.el.shadowRoot.querySelectorAll('.calendar__next-button')[1];
|
||||
return this.el.shadowRoot?.querySelectorAll('.calendar__next-button')[1];
|
||||
}
|
||||
|
||||
get previousMonthButtonEl() {
|
||||
return this.el.shadowRoot.querySelectorAll('.calendar__previous-button')[1];
|
||||
return this.el.shadowRoot?.querySelectorAll('.calendar__previous-button')[1];
|
||||
}
|
||||
|
||||
get gridEl() {
|
||||
return this.el.shadowRoot.querySelector('.calendar__grid');
|
||||
return this.el.shadowRoot?.querySelector('.calendar__grid');
|
||||
}
|
||||
|
||||
get weekdayHeaderEls() {
|
||||
return [].slice.call(this.el.shadowRoot.querySelectorAll('.calendar__weekday-header'));
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll('.calendar__weekday-header'),
|
||||
));
|
||||
}
|
||||
|
||||
get dayEls() {
|
||||
return [].slice.call(
|
||||
this.el.shadowRoot.querySelectorAll('.calendar__day-button[current-month]'),
|
||||
);
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[current-month]',
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
get previousMonthDayEls() {
|
||||
return [].slice.call(
|
||||
this.el.shadowRoot.querySelectorAll('.calendar__day-button[previous-month]'),
|
||||
);
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[previous-month]',
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
get nextMonthDayEls() {
|
||||
return [].slice.call(this.el.shadowRoot.querySelectorAll('.calendar__day-button[next-month]'));
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[next-month]',
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
get dayObjs() {
|
||||
|
|
@ -81,19 +98,25 @@ export class CalendarObject {
|
|||
return this.nextMonthDayEls.map(d => new DayObject(d));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} monthDayNumber
|
||||
*/
|
||||
getDayEl(monthDayNumber) {
|
||||
// Relies on the fact that empty cells don't have .calendar__day-button[current-month]
|
||||
return this.el.shadowRoot.querySelectorAll('.calendar__day-button[current-month]')[
|
||||
monthDayNumber - 1
|
||||
];
|
||||
return /** @type {HTMLElement} */ (this.el.shadowRoot?.querySelectorAll(
|
||||
'.calendar__day-button[current-month]',
|
||||
)[monthDayNumber - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} monthDayNumber
|
||||
*/
|
||||
getDayObj(monthDayNumber) {
|
||||
return new DayObject(this.getDayEl(monthDayNumber));
|
||||
return new DayObject(/** @type{HTMLElement} */ (this.getDayEl(monthDayNumber)));
|
||||
}
|
||||
|
||||
get selectedDayObj() {
|
||||
return this.dayObjs.find(d => d.selected);
|
||||
return this.dayObjs.find(d => d.isSelected);
|
||||
}
|
||||
|
||||
get centralDayObj() {
|
||||
|
|
@ -101,7 +124,7 @@ export class CalendarObject {
|
|||
}
|
||||
|
||||
get focusedDayObj() {
|
||||
return this.dayObjs.find(d => d.el === this.el.shadowRoot.activeElement);
|
||||
return this.dayObjs.find(d => d.el === this.el.shadowRoot?.activeElement);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,7 +132,7 @@ export class CalendarObject {
|
|||
*
|
||||
* @param {function} condition : condition that should apply for "filter" days
|
||||
* - Example: "(dayObj) => dayObj.selected"
|
||||
* @param {array|function} filter - month day numbers for which condition should apply.
|
||||
* @param {number[]|function} [filter] - month day numbers for which condition should apply.
|
||||
* - Example 1: "[15, 20]"
|
||||
* - Example 2: "(dayNumber) => dayNumber === 15" (1 based ,not zero based)
|
||||
*/
|
||||
|
|
@ -130,10 +153,10 @@ export class CalendarObject {
|
|||
* States
|
||||
*/
|
||||
get activeMonth() {
|
||||
return this.monthHeadingEl.textContent.trim();
|
||||
return this.monthHeadingEl?.textContent?.trim();
|
||||
}
|
||||
|
||||
get activeYear() {
|
||||
return this.yearHeadingEl.textContent.trim();
|
||||
return this.yearHeadingEl?.textContent?.trim();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { weekdayNames } from './weekdayNames.js';
|
|||
* allows for writing readable, 'DOM structure agnostic' tests
|
||||
*/
|
||||
export class DayObject {
|
||||
/**
|
||||
* @param {HTMLElement} dayEl
|
||||
*/
|
||||
constructor(dayEl) {
|
||||
this.el = dayEl;
|
||||
}
|
||||
|
|
@ -14,11 +17,12 @@ export class DayObject {
|
|||
*/
|
||||
|
||||
get calendarShadowRoot() {
|
||||
return this.el.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||
return this.el.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode
|
||||
?.parentNode;
|
||||
}
|
||||
|
||||
get cellEl() {
|
||||
return this.el.parentElement;
|
||||
return /** @type {HTMLElement} */ (this.el.parentElement);
|
||||
}
|
||||
|
||||
get buttonEl() {
|
||||
|
|
@ -46,7 +50,7 @@ export class DayObject {
|
|||
}
|
||||
|
||||
get isFocused() {
|
||||
return this.calendarShadowRoot.activeElement === this.buttonEl;
|
||||
return /** @type {ShadowRoot} */ (this.calendarShadowRoot).activeElement === this.buttonEl;
|
||||
}
|
||||
|
||||
get monthday() {
|
||||
|
|
@ -59,17 +63,21 @@ export class DayObject {
|
|||
|
||||
get weekdayNameShort() {
|
||||
const weekdayEls = Array.from(
|
||||
this.el.parentElement.parentElement.querySelectorAll('.calendar__day-cell'),
|
||||
/** @type {HTMLElement} */ (this.el.parentElement?.parentElement).querySelectorAll(
|
||||
'.calendar__day-cell',
|
||||
),
|
||||
);
|
||||
const dayIndex = weekdayEls.indexOf(this.el.parentElement);
|
||||
const dayIndex = weekdayEls.indexOf(/** @type {HTMLElement} */ (this.el.parentElement));
|
||||
return weekdayNames['en-GB'].Sunday.short[dayIndex];
|
||||
}
|
||||
|
||||
get weekdayNameLong() {
|
||||
const weekdayEls = Array.from(
|
||||
this.el.parentElement.parentElement.querySelectorAll('.calendar__day-cell'),
|
||||
/** @type {HTMLElement} */ (this.el.parentElement?.parentElement).querySelectorAll(
|
||||
'.calendar__day-cell',
|
||||
),
|
||||
);
|
||||
const dayIndex = weekdayEls.indexOf(this.el.parentElement);
|
||||
const dayIndex = weekdayEls.indexOf(/** @type {HTMLElement} */ (this.el.parentElement));
|
||||
return weekdayNames['en-GB'].Sunday.long[dayIndex];
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +85,8 @@ export class DayObject {
|
|||
* Other
|
||||
*/
|
||||
get cellIndex() {
|
||||
return Array.from(this.cellEl.parentElement.children).indexOf(this.cellEl);
|
||||
return Array.from(/** @type {HTMLElement} */ (this.cellEl.parentElement).children).indexOf(
|
||||
this.cellEl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,34 @@ import { html } from '@lion/core';
|
|||
import '@lion/core/test-helpers/keyboardEventShimIE.js';
|
||||
import { localize } from '@lion/localize';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers.js';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import '../lion-calendar.js';
|
||||
import { isSameDate } from '../src/utils/isSameDate.js';
|
||||
import { CalendarObject, DayObject } from '../test-helpers.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionCalendar').LionCalendar} LionCalendar
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionCalendar>} */ (_fixture);
|
||||
|
||||
describe('<lion-calendar>', () => {
|
||||
beforeEach(() => {
|
||||
localizeTearDown();
|
||||
});
|
||||
|
||||
describe('Structure', () => {
|
||||
describe.skip('Structure', () => {
|
||||
it('implements BEM structure', async () => {
|
||||
const el = await fixture(html`<lion-calendar></lion-calendar>`);
|
||||
|
||||
expect(el.shadowRoot.querySelector('.calendar')).to.exist;
|
||||
expect(el.shadowRoot.querySelector('.calendar__navigation')).to.exist;
|
||||
expect(el.shadowRoot.querySelector('.calendar__previous-button')).to.exist;
|
||||
expect(el.shadowRoot.querySelector('.calendar__next-button')).to.exist;
|
||||
expect(el.shadowRoot.querySelector('.calendar__navigation-heading')).to.exist;
|
||||
expect(el.shadowRoot.querySelector('.calendar__grid')).to.exist;
|
||||
expect(el.shadowRoot?.querySelector('.calendar')).to.exist;
|
||||
expect(el.shadowRoot?.querySelector('.calendar__navigation')).to.exist;
|
||||
expect(el.shadowRoot?.querySelector('.calendar__previous-button')).to.exist;
|
||||
expect(el.shadowRoot?.querySelector('.calendar__next-button')).to.exist;
|
||||
expect(el.shadowRoot?.querySelector('.calendar__navigation-heading')).to.exist;
|
||||
expect(el.shadowRoot?.querySelector('.calendar__grid')).to.exist;
|
||||
});
|
||||
|
||||
it('has heading with month and year', async () => {
|
||||
|
|
@ -30,7 +37,7 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
const el = await fixture(html`<lion-calendar></lion-calendar>`);
|
||||
|
||||
expect(el.shadowRoot.querySelector('#year')).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelector('#year')).dom.to.equal(`
|
||||
<h2
|
||||
id="year"
|
||||
class="calendar__navigation-heading"
|
||||
|
|
@ -39,7 +46,7 @@ describe('<lion-calendar>', () => {
|
|||
2000
|
||||
</h2>
|
||||
`);
|
||||
expect(el.shadowRoot.querySelector('#month')).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelector('#month')).dom.to.equal(`
|
||||
<h2
|
||||
id="month"
|
||||
class="calendar__navigation-heading"
|
||||
|
|
@ -56,7 +63,7 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(
|
||||
html`<lion-calendar .centralDate=${new Date('2019/11/20')}></lion-calendar>`,
|
||||
);
|
||||
expect(el.shadowRoot.querySelectorAll('.calendar__previous-button')[0]).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelectorAll('.calendar__previous-button')[0]).dom.to.equal(`
|
||||
<button class="calendar__previous-button" aria-label="Previous year, November 2018" title="Previous year, November 2018"><</button>
|
||||
`);
|
||||
});
|
||||
|
|
@ -65,7 +72,7 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(
|
||||
html`<lion-calendar .centralDate=${new Date('2019/11/20')}></lion-calendar>`,
|
||||
);
|
||||
expect(el.shadowRoot.querySelectorAll('.calendar__next-button')[0]).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelectorAll('.calendar__next-button')[0]).dom.to.equal(`
|
||||
<button class="calendar__next-button" aria-label="Next year, November 2020" title="Next year, November 2020">></button>
|
||||
`);
|
||||
});
|
||||
|
|
@ -74,7 +81,7 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(
|
||||
html`<lion-calendar .centralDate=${new Date('2019/11/20')}></lion-calendar>`,
|
||||
);
|
||||
expect(el.shadowRoot.querySelectorAll('.calendar__previous-button')[1]).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelectorAll('.calendar__previous-button')[1]).dom.to.equal(`
|
||||
<button class="calendar__previous-button" aria-label="Previous month, October 2019" title="Previous month, October 2019"><</button>
|
||||
`);
|
||||
});
|
||||
|
|
@ -83,7 +90,7 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(
|
||||
html`<lion-calendar .centralDate=${new Date('2019/11/20')}></lion-calendar>`,
|
||||
);
|
||||
expect(el.shadowRoot.querySelectorAll('.calendar__next-button')[1]).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelectorAll('.calendar__next-button')[1]).dom.to.equal(`
|
||||
<button class="calendar__next-button" aria-label="Next month, December 2019" title="Next month, December 2019">></button>
|
||||
`);
|
||||
});
|
||||
|
|
@ -120,14 +127,14 @@ describe('<lion-calendar>', () => {
|
|||
);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
o => o.buttonEl.getAttribute('tabindex') === '0',
|
||||
n => n === 5,
|
||||
/** @param {DayObject} o */ o => o.buttonEl.getAttribute('tabindex') === '0',
|
||||
/** @param {number} n */ n => n === 5,
|
||||
),
|
||||
).to.be.true;
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
o => o.buttonEl.getAttribute('tabindex') === '-1',
|
||||
n => n !== 5,
|
||||
/** @param {DayObject} o */ o => o.buttonEl.getAttribute('tabindex') === '-1',
|
||||
/** @param {number} n */ n => n !== 5,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
|
|
@ -222,7 +229,7 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
it('doesn\'t send event "user-selected-date-changed" when user selects a disabled date', async () => {
|
||||
const dateChangedSpy = sinon.spy();
|
||||
const disable15th = d => d.getDate() === 15;
|
||||
const disable15th = /** @param {Date} d */ d => d.getDate() === 15;
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.selectedDate="${new Date('2000/12/12')}"
|
||||
|
|
@ -243,14 +250,16 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(el.focusedDate).to.be.null;
|
||||
elObj.getDayEl(15).click();
|
||||
expect(isSameDate(el.focusedDate, new Date('2019/06/15'))).to.equal(true);
|
||||
expect(isSameDate(/** @type {Date} */ (el.focusedDate), new Date('2019/06/15'))).to.equal(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('has a focusDate() method to focus an arbitrary date', async () => {
|
||||
const el = await fixture(html`<lion-calendar></lion-calendar>`);
|
||||
const elObj = new CalendarObject(el);
|
||||
await el.focusDate(new Date('2016/06/10'));
|
||||
expect(isSameDate(el.focusedDate, new Date('2016/06/10'))).to.be.true;
|
||||
expect(isSameDate(/** @type {Date} */ (el.focusedDate), new Date('2016/06/10'))).to.be.true;
|
||||
expect(elObj.getDayObj(10).isFocused).to.be.true;
|
||||
});
|
||||
|
||||
|
|
@ -263,7 +272,7 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
el.focusCentralDate();
|
||||
expect(isSameDate(el.focusedDate, new Date('2015/12/02'))).to.be.true;
|
||||
expect(isSameDate(/** @type {Date} */ (el.focusedDate), new Date('2015/12/02'))).to.be.true;
|
||||
expect(elObj.getDayObj(2).isFocused).to.be.true;
|
||||
});
|
||||
|
||||
|
|
@ -276,7 +285,7 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
await el.focusSelectedDate();
|
||||
expect(isSameDate(el.focusedDate, new Date('2014/07/07'))).to.be.true;
|
||||
expect(isSameDate(/** @type {Date} */ (el.focusedDate), new Date('2014/07/07'))).to.be.true;
|
||||
expect(elObj.getDayObj(7).isFocused).to.be.true;
|
||||
});
|
||||
|
||||
|
|
@ -314,6 +323,7 @@ describe('<lion-calendar>', () => {
|
|||
});
|
||||
|
||||
it('disables a date with disableDates function', async () => {
|
||||
/** @param {Date} d */
|
||||
const disable15th = d => d.getDate() === 15;
|
||||
const el = await fixture(
|
||||
html`
|
||||
|
|
@ -343,7 +353,7 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.selectedDate="${new Date('2001/01/08')}"
|
||||
.disableDates=${day => day.getDate() === 3}
|
||||
.disableDates=${/** @param {Date} date */ date => date.getDate() === 3}
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
|
@ -370,6 +380,7 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
describe('Normalization', () => {
|
||||
it('normalizes all generated dates', async () => {
|
||||
/** @param {Date} d */
|
||||
function isNormalizedDate(d) {
|
||||
return d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0;
|
||||
}
|
||||
|
|
@ -433,7 +444,7 @@ describe('<lion-calendar>', () => {
|
|||
describe('Accessibility', () => {
|
||||
it('has aria-atomic="true" set on the secondary title', async () => {
|
||||
const elObj = new CalendarObject(await fixture(html`<lion-calendar></lion-calendar>`));
|
||||
expect(elObj.monthHeadingEl.getAttribute('aria-atomic')).to.equal('true');
|
||||
expect(elObj.monthHeadingEl?.getAttribute('aria-atomic')).to.equal('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -449,12 +460,12 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
elObj.previousYearButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousYearButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
elObj.previousYearButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousYearButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('1999');
|
||||
|
|
@ -469,12 +480,12 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
elObj.nextYearButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.nextYearButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
elObj.nextYearButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.nextYearButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2002');
|
||||
|
|
@ -487,17 +498,17 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.activeMonth).to.equal('June');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
expect(elObj.previousYearButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextYearButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.previousYearButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextYearButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
|
||||
el.minDate = new Date('2000/01/01');
|
||||
el.maxDate = new Date('2000/12/31');
|
||||
await el.updateComplete;
|
||||
|
||||
expect(elObj.previousYearButtonEl.hasAttribute('disabled')).to.equal(true);
|
||||
expect(elObj.nextYearButtonEl.hasAttribute('disabled')).to.equal(true);
|
||||
expect(elObj.previousYearButtonEl?.hasAttribute('disabled')).to.equal(true);
|
||||
expect(elObj.nextYearButtonEl?.hasAttribute('disabled')).to.equal(true);
|
||||
|
||||
elObj.previousYearButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousYearButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('June');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
|
@ -506,8 +517,8 @@ describe('<lion-calendar>', () => {
|
|||
el.maxDate = new Date('2001/01/01');
|
||||
await el.updateComplete;
|
||||
|
||||
expect(elObj.previousYearButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextYearButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.previousYearButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextYearButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
});
|
||||
|
||||
it('sets label to correct month previousYearButton and nextYearButton based on disabled days accordingly', async () => {
|
||||
|
|
@ -519,9 +530,9 @@ describe('<lion-calendar>', () => {
|
|||
el.maxDate = new Date('2001/05/12');
|
||||
await el.updateComplete;
|
||||
|
||||
expect(elObj.previousYearButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.previousYearButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.previousYearButtonEl.ariaLabel).to.equal('Previous year, July 1999');
|
||||
expect(elObj.nextYearButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextYearButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextYearButtonEl.ariaLabel).to.equal('Next year, May 2001');
|
||||
});
|
||||
});
|
||||
|
|
@ -536,12 +547,12 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
elObj.previousMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
elObj.previousMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('November');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
|
@ -555,13 +566,12 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.nextMonthButtonEl).not.to.equal(null);
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
elObj.nextMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.nextMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
elObj.nextMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.nextMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('February');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
|
@ -574,22 +584,20 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.previousMonthButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextMonthButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
|
||||
el.minDate = new Date('2000/12/01');
|
||||
el.maxDate = new Date('2000/12/31');
|
||||
await el.updateComplete;
|
||||
|
||||
expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(true);
|
||||
expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(true);
|
||||
|
||||
elObj.previousMonthButtonEl.click();
|
||||
expect(elObj.previousMonthButtonEl?.hasAttribute('disabled')).to.equal(true);
|
||||
expect(elObj.nextMonthButtonEl?.hasAttribute('disabled')).to.equal(true);
|
||||
/** @type {HTMLElement} */ (elObj.previousMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
elObj.previousMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
|
@ -606,10 +614,10 @@ describe('<lion-calendar>', () => {
|
|||
el.minDate = new Date('2000/11/20');
|
||||
await el.updateComplete;
|
||||
|
||||
expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.previousMonthButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(isSameDate(el.centralDate, new Date('2000/12/15'))).to.be.true;
|
||||
|
||||
elObj.previousMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.previousMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('November');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
|
@ -629,10 +637,9 @@ describe('<lion-calendar>', () => {
|
|||
el.maxDate = new Date('2001/01/10');
|
||||
await el.updateComplete;
|
||||
|
||||
expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(false);
|
||||
expect(elObj.nextMonthButtonEl?.hasAttribute('disabled')).to.equal(false);
|
||||
expect(isSameDate(el.centralDate, new Date('2000/12/15'))).to.be.true;
|
||||
|
||||
elObj.nextMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (elObj.nextMonthButtonEl).click();
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
|
@ -649,12 +656,12 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
// when
|
||||
const remote = new CalendarObject(element);
|
||||
remote.nextMonthButtonEl.click();
|
||||
/** @type {HTMLElement} */ (remote.nextMonthButtonEl).click();
|
||||
await element.updateComplete;
|
||||
// then
|
||||
expect(remote.activeMonth).to.equal('September');
|
||||
expect(remote.activeYear).to.equal('2019');
|
||||
expect(remote.centralDayObj.el).dom.to.equal(`
|
||||
expect(remote.centralDayObj?.el).dom.to.equal(`
|
||||
<button
|
||||
class="calendar__day-button"
|
||||
tabindex="0"
|
||||
|
|
@ -677,16 +684,16 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
// month
|
||||
expect(elObj.previousMonthButtonEl.getAttribute('title')).to.equal(
|
||||
expect(elObj.previousMonthButtonEl?.getAttribute('title')).to.equal(
|
||||
'Previous month, November 2000',
|
||||
);
|
||||
expect(elObj.previousMonthButtonEl.getAttribute('aria-label')).to.equal(
|
||||
expect(elObj.previousMonthButtonEl?.getAttribute('aria-label')).to.equal(
|
||||
'Previous month, November 2000',
|
||||
);
|
||||
expect(elObj.nextMonthButtonEl.getAttribute('title')).to.equal(
|
||||
expect(elObj.nextMonthButtonEl?.getAttribute('title')).to.equal(
|
||||
'Next month, January 2001',
|
||||
);
|
||||
expect(elObj.nextMonthButtonEl.getAttribute('aria-label')).to.equal(
|
||||
expect(elObj.nextMonthButtonEl?.getAttribute('aria-label')).to.equal(
|
||||
'Next month, January 2001',
|
||||
);
|
||||
|
||||
|
|
@ -712,7 +719,7 @@ describe('<lion-calendar>', () => {
|
|||
html`<lion-calendar .selectedDate="${new Date('2000/12/12')}"></lion-calendar>`,
|
||||
);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.weekdayHeaderEls.map(h => h.textContent.trim())).to.deep.equal([
|
||||
expect(elObj.weekdayHeaderEls.map(h => h.textContent?.trim())).to.deep.equal([
|
||||
'Sun',
|
||||
'Mon',
|
||||
'Tue',
|
||||
|
|
@ -731,7 +738,9 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.getDayEl(15).hasAttribute('today')).to.be.true;
|
||||
|
||||
expect(elObj.checkForAllDayObjs(d => d.isToday, [15])).to.equal(true);
|
||||
expect(elObj.checkForAllDayObjs(/** @param {DayObject} d */ d => d.isToday, [15])).to.equal(
|
||||
true,
|
||||
);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
|
@ -741,15 +750,21 @@ describe('<lion-calendar>', () => {
|
|||
html`<lion-calendar .selectedDate="${new Date('2000/12/12')}"></lion-calendar>`,
|
||||
);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.checkForAllDayObjs(obj => obj.el.hasAttribute('selected'), [12])).to.equal(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
/** @param {DayObject} obj */ obj => obj.el.hasAttribute('selected'),
|
||||
[12],
|
||||
),
|
||||
).to.equal(true);
|
||||
|
||||
el.selectedDate = new Date('2000/12/15');
|
||||
await el.updateComplete;
|
||||
expect(elObj.checkForAllDayObjs(obj => obj.el.hasAttribute('selected'), [15])).to.equal(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
/** @param {DayObject} obj */ obj => obj.el.hasAttribute('selected'),
|
||||
[15],
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('adds "disabled" attribute to disabled dates', async () => {
|
||||
|
|
@ -765,7 +780,12 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(d => d.el.hasAttribute('disabled'), [1, 2, 30, 31]),
|
||||
elObj.checkForAllDayObjs(/** @param {DayObject} d */ d => d.el.hasAttribute('disabled'), [
|
||||
1,
|
||||
2,
|
||||
30,
|
||||
31,
|
||||
]),
|
||||
).to.equal(true);
|
||||
|
||||
clock.restore();
|
||||
|
|
@ -782,7 +802,10 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(d => d.buttonEl.getAttribute('tabindex') === '0', [12]),
|
||||
elObj.checkForAllDayObjs(
|
||||
/** @param {DayObject} d */ d => d.buttonEl.getAttribute('tabindex') === '0',
|
||||
[12],
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
|
|
@ -793,8 +816,8 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
d => d.buttonEl.getAttribute('tabindex') === '-1',
|
||||
dayNumber => dayNumber !== 12,
|
||||
/** @param {DayObject} d */ d => d.buttonEl.getAttribute('tabindex') === '-1',
|
||||
/** @param {number} dayNumber */ dayNumber => dayNumber !== 12,
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
|
@ -810,8 +833,8 @@ describe('<lion-calendar>', () => {
|
|||
const elObj = new CalendarObject(el);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
d => d.buttonEl.getAttribute('tabindex') === '-1',
|
||||
dayNumber => dayNumber < 9,
|
||||
/** @param {DayObject} d */ d => d.buttonEl.getAttribute('tabindex') === '-1',
|
||||
/** @param {number} dayNumber */ dayNumber => dayNumber < 9,
|
||||
),
|
||||
).to.equal(true);
|
||||
});
|
||||
|
|
@ -824,12 +847,14 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'PageUp' }));
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'PageUp' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'PageDown' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
|
|
@ -845,14 +870,14 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'PageDown', altKey: true }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2002');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'PageUp', altKey: true }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
|
|
@ -867,11 +892,11 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(2 + 7);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(2 + 7);
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to previous row item via [arrow up] key', async () => {
|
||||
|
|
@ -880,11 +905,11 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowUp' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(26); // of month before
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(26); // of month before
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to previous column item via [arrow left] key', async () => {
|
||||
|
|
@ -894,11 +919,11 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowLeft' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(12 - 1);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(12 - 1);
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to next column item via [arrow right] key', async () => {
|
||||
|
|
@ -908,27 +933,28 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
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 () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.selectedDate="${new Date('2001/01/02')}"
|
||||
.disableDates=${day => day.getDate() === 3 || day.getDate() === 4}
|
||||
.disableDates=${/** @param {Date} date */ date =>
|
||||
date.getDate() === 3 || date.getDate() === 4}
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(5);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(5);
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to next row via [arrow right] key if last item in row', async () => {
|
||||
|
|
@ -936,14 +962,14 @@ describe('<lion-calendar>', () => {
|
|||
<lion-calendar .selectedDate="${new Date('2019/01/05')}"></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj.weekdayNameShort).to.equal('Sat');
|
||||
expect(elObj.centralDayObj?.weekdayNameShort).to.equal('Sat');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(6);
|
||||
expect(elObj.focusedDayObj.weekdayNameShort).to.equal('Sun');
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(6);
|
||||
expect(elObj.focusedDayObj?.weekdayNameShort).to.equal('Sun');
|
||||
});
|
||||
|
||||
it('navigates (sets focus) to previous row via [arrow left] key if first item in row', async () => {
|
||||
|
|
@ -951,14 +977,14 @@ describe('<lion-calendar>', () => {
|
|||
<lion-calendar .selectedDate="${new Date('2019/01/06')}"></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj.weekdayNameShort).to.equal('Sun');
|
||||
expect(elObj.centralDayObj?.weekdayNameShort).to.equal('Sun');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowLeft' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(5);
|
||||
expect(elObj.focusedDayObj.weekdayNameShort).to.equal('Sat');
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(5);
|
||||
expect(elObj.focusedDayObj?.weekdayNameShort).to.equal('Sat');
|
||||
});
|
||||
|
||||
it('navigates to next month via [arrow right] key if last day of month', async () => {
|
||||
|
|
@ -969,13 +995,13 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(1);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('navigates to previous month via [arrow left] key if first day of month', async () => {
|
||||
|
|
@ -986,13 +1012,13 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowLeft' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(31);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(31);
|
||||
});
|
||||
|
||||
it('navigates to next month via [arrow down] key if last row of month', async () => {
|
||||
|
|
@ -1003,13 +1029,13 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(6);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(6);
|
||||
});
|
||||
|
||||
it('navigates to previous month via [arrow up] key if first row of month', async () => {
|
||||
|
|
@ -1020,13 +1046,13 @@ describe('<lion-calendar>', () => {
|
|||
expect(elObj.activeMonth).to.equal('January');
|
||||
expect(elObj.activeYear).to.equal('2001');
|
||||
|
||||
el.__contentWrapperElement.dispatchEvent(
|
||||
el.__contentWrapperElement?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowUp' }),
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('December');
|
||||
expect(elObj.activeYear).to.equal('2000');
|
||||
expect(elObj.focusedDayObj.monthday).to.equal(26);
|
||||
expect(elObj.focusedDayObj?.monthday).to.equal(26);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1037,7 +1063,7 @@ describe('<lion-calendar>', () => {
|
|||
<lion-calendar .selectedDate=${new Date('2019/06/15')}></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj.monthday).to.equal(15);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(15);
|
||||
});
|
||||
|
||||
it('is today if no selected date is available', async () => {
|
||||
|
|
@ -1045,7 +1071,7 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
const el = await fixture(html`<lion-calendar></lion-calendar>`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj.monthday).to.equal(15);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(15);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
|
@ -1054,25 +1080,27 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-calendar
|
||||
.centralDate="${new Date('2000/12/15')}"
|
||||
.disableDates="${d => d.getDate() <= 16}"
|
||||
.disableDates="${/** @param {Date} d */ d => d.getDate() <= 16}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj.monthday).to.equal(17);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(17);
|
||||
|
||||
el.disableDates = d => d.getDate() >= 12;
|
||||
await el.updateComplete;
|
||||
expect(elObj.centralDayObj.monthday).to.equal(11);
|
||||
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="${d => d.getDate() === 15}"></lion-calendar>
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getDate() === 15}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.centralDayObj.monthday).to.equal(16);
|
||||
expect(elObj.centralDayObj?.monthday).to.equal(16);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
|
@ -1081,7 +1109,9 @@ describe('<lion-calendar>', () => {
|
|||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar .disableDates="${d => d.getFullYear() > 1998}"></lion-calendar>
|
||||
<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);
|
||||
|
|
@ -1094,7 +1124,9 @@ describe('<lion-calendar>', () => {
|
|||
const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() });
|
||||
|
||||
const el = await fixture(html`
|
||||
<lion-calendar .disableDates="${d => d.getFullYear() < 2002}"></lion-calendar>
|
||||
<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);
|
||||
|
|
@ -1105,7 +1137,9 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
it('throws if no available date can be found within +/- 750 days', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-calendar .disableDates="${d => d.getFullYear() < 2002}"></lion-calendar>
|
||||
<lion-calendar
|
||||
.disableDates="${/** @param {Date} d */ d => d.getFullYear() < 2002}"
|
||||
></lion-calendar>
|
||||
`);
|
||||
|
||||
expect(() => {
|
||||
|
|
@ -1134,14 +1168,14 @@ describe('<lion-calendar>', () => {
|
|||
// next/previous month.
|
||||
it('has role="application" to activate keyboard navigation', async () => {
|
||||
const elObj = new CalendarObject(await fixture(html`<lion-calendar></lion-calendar>`));
|
||||
expect(elObj.rootEl.getAttribute('role')).to.equal('application');
|
||||
expect(elObj.rootEl?.getAttribute('role')).to.equal('application');
|
||||
});
|
||||
|
||||
it(`renders the calendar as a table element with role="grid", aria-readonly="true" and
|
||||
a caption (month + year)`, async () => {
|
||||
const elObj = new CalendarObject(await fixture(html`<lion-calendar></lion-calendar>`));
|
||||
expect(elObj.gridEl.getAttribute('role')).to.equal('grid');
|
||||
expect(elObj.gridEl.getAttribute('aria-readonly')).to.equal('true');
|
||||
expect(elObj.gridEl?.getAttribute('role')).to.equal('grid');
|
||||
expect(elObj.gridEl?.getAttribute('aria-readonly')).to.equal('true');
|
||||
});
|
||||
|
||||
it('adds aria-labels to the weekday table headers', async () => {
|
||||
|
|
@ -1159,7 +1193,7 @@ 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 = d => d.el.tagName === 'BUTTON';
|
||||
const hasBtn = /** @param {DayObject} d */ d => d.el.tagName === 'BUTTON';
|
||||
expect(elObj.checkForAllDayObjs(hasBtn)).to.equal(true);
|
||||
});
|
||||
|
||||
|
|
@ -1188,7 +1222,8 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
it('sets aria-current="date" to todays button', async () => {
|
||||
const elObj = new CalendarObject(await fixture(html`<lion-calendar></lion-calendar>`));
|
||||
const hasAriaCurrent = d => d.buttonEl.getAttribute('aria-current') === 'date';
|
||||
const hasAriaCurrent = /** @param {DayObject} d */ d =>
|
||||
d.buttonEl.getAttribute('aria-current') === 'date';
|
||||
const monthday = new Date().getDate();
|
||||
expect(elObj.checkForAllDayObjs(hasAriaCurrent, [monthday])).to.equal(true);
|
||||
});
|
||||
|
|
@ -1199,7 +1234,8 @@ describe('<lion-calendar>', () => {
|
|||
<lion-calendar .selectedDate="${new Date('2000/11/12')}"></lion-calendar>
|
||||
`),
|
||||
);
|
||||
const hasAriaPressed = d => d.buttonEl.getAttribute('aria-pressed') === 'true';
|
||||
const hasAriaPressed = /** @param {DayObject} d */ d =>
|
||||
d.buttonEl.getAttribute('aria-pressed') === 'true';
|
||||
expect(elObj.checkForAllDayObjs(hasAriaPressed, [12])).to.equal(true);
|
||||
});
|
||||
|
||||
|
|
@ -1221,7 +1257,7 @@ describe('<lion-calendar>', () => {
|
|||
);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(
|
||||
d =>
|
||||
/** @param {DayObject} d */ d =>
|
||||
d.buttonEl.getAttribute('aria-label') ===
|
||||
`${d.monthday} November 2000 ${d.weekdayNameLong}`,
|
||||
),
|
||||
|
|
@ -1256,7 +1292,7 @@ describe('<lion-calendar>', () => {
|
|||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('décembre');
|
||||
|
||||
el.locale = undefined;
|
||||
el.locale = '';
|
||||
await el.updateComplete;
|
||||
expect(elObj.activeMonth).to.equal('prosinec');
|
||||
});
|
||||
|
|
@ -1267,11 +1303,11 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(elObj.nextMonthButtonEl.getAttribute('aria-label')).to.equal(
|
||||
expect(elObj.nextMonthButtonEl?.getAttribute('aria-label')).to.equal(
|
||||
'Next month, December 2019',
|
||||
);
|
||||
|
||||
expect(elObj.previousMonthButtonEl.getAttribute('aria-label')).to.equal(
|
||||
expect(elObj.previousMonthButtonEl?.getAttribute('aria-label')).to.equal(
|
||||
'Previous month, October 2019',
|
||||
);
|
||||
|
||||
|
|
@ -1285,7 +1321,7 @@ describe('<lion-calendar>', () => {
|
|||
'Saturday',
|
||||
]);
|
||||
|
||||
expect(elObj.weekdayHeaderEls.map(h => h.textContent.trim())).to.deep.equal([
|
||||
expect(elObj.weekdayHeaderEls.map(h => h.textContent?.trim())).to.deep.equal([
|
||||
'Sun',
|
||||
'Mon',
|
||||
'Tue',
|
||||
|
|
@ -1297,11 +1333,11 @@ describe('<lion-calendar>', () => {
|
|||
|
||||
localize.locale = 'nl-NL';
|
||||
await el.updateComplete;
|
||||
expect(elObj.nextMonthButtonEl.getAttribute('aria-label')).to.equal(
|
||||
expect(elObj.nextMonthButtonEl?.getAttribute('aria-label')).to.equal(
|
||||
'Volgende maand, december 2019',
|
||||
);
|
||||
|
||||
expect(elObj.previousMonthButtonEl.getAttribute('aria-label')).to.equal(
|
||||
expect(elObj.previousMonthButtonEl?.getAttribute('aria-label')).to.equal(
|
||||
'Vorige maand, oktober 2019',
|
||||
);
|
||||
|
||||
|
|
@ -1315,7 +1351,7 @@ describe('<lion-calendar>', () => {
|
|||
'zaterdag',
|
||||
]);
|
||||
|
||||
expect(elObj.weekdayHeaderEls.map(h => h.textContent.trim())).to.deep.equal([
|
||||
expect(elObj.weekdayHeaderEls.map(h => h.textContent?.trim())).to.deep.equal([
|
||||
'zo',
|
||||
'ma',
|
||||
'di',
|
||||
|
|
@ -1346,7 +1382,8 @@ describe('<lion-calendar>', () => {
|
|||
const el = await fixture(
|
||||
html`
|
||||
<lion-calendar
|
||||
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
|
||||
.disableDates=${/** @param {Date} date */ date =>
|
||||
date.getDay() === 6 || date.getDay() === 0}
|
||||
></lion-calendar>
|
||||
`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ import { expect } from '@open-wc/testing';
|
|||
import { createMonth } from '../../src/utils/createMonth.js';
|
||||
import { createWeek } from '../../src/utils/createWeek.js';
|
||||
|
||||
/**
|
||||
* @param {import('../../types/day').Month} obj
|
||||
*/
|
||||
function compareMonth(obj) {
|
||||
obj.weeks.forEach((week, weeki) => {
|
||||
week.days.forEach((day, dayi) => {
|
||||
// @ts-expect-error since we are converting Date to ISO string, but that's okay for our test Date comparisons
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
obj.weeks[weeki].days[dayi].date = obj.weeks[weeki].days[dayi].date.toISOString();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ import { expect } from '@open-wc/testing';
|
|||
import { createMultipleMonth } from '../../src/utils/createMultipleMonth.js';
|
||||
import { createMonth } from '../../src/utils/createMonth.js';
|
||||
|
||||
/**
|
||||
* @param {{ months: import('../../types/day').Month[]}} obj
|
||||
*/
|
||||
function compareMultipleMonth(obj) {
|
||||
obj.months.forEach((month, monthi) => {
|
||||
month.weeks.forEach((week, weeki) => {
|
||||
week.days.forEach((day, dayi) => {
|
||||
// @ts-expect-error since we are converting Date to ISO string, but that's okay for our test Date comparisons
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
obj.months[monthi].weeks[weeki].days[dayi].date = obj.months[monthi].weeks[weeki].days[
|
||||
dayi
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ import { expect } from '@open-wc/testing';
|
|||
import { createWeek } from '../../src/utils/createWeek.js';
|
||||
import { createDay } from '../../src/utils/createDay.js';
|
||||
|
||||
/**
|
||||
* @param {import('../../types/day').Week} obj
|
||||
*/
|
||||
function compareWeek(obj) {
|
||||
for (let i = 0; i < 7; i += 1) {
|
||||
// @ts-expect-error since we are converting Date to ISO string, but that's okay for our test Date comparisons
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
obj.days[i].date = obj.days[i].date.toISOString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,4 @@ describe('isSameDate', () => {
|
|||
expect(isSameDate(day1, day2)).to.be.true;
|
||||
expect(isSameDate(day1, day3)).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false if not a date is provided', () => {
|
||||
const day = new Date('2001/01/01');
|
||||
expect(isSameDate(day, undefined)).to.be.false;
|
||||
expect(isSameDate(undefined, day)).to.be.false;
|
||||
expect(isSameDate(undefined, undefined)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const html = strings => strings[0];
|
||||
const html = /** @param {TemplateStringsArray} strings */ strings => strings[0];
|
||||
|
||||
export default html`
|
||||
<div id="js-content-wrapper">
|
||||
|
|
|
|||
25
packages/calendar/types/day.d.ts
vendored
Normal file
25
packages/calendar/types/day.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
export declare interface Day {
|
||||
weekOrder?: number;
|
||||
central?: boolean;
|
||||
date: Date;
|
||||
startOfWeek?: boolean;
|
||||
selected?: boolean;
|
||||
previousMonth?: boolean;
|
||||
currentMonth?: boolean;
|
||||
nextMonth?: boolean;
|
||||
past?: boolean;
|
||||
today?: boolean;
|
||||
future?: boolean;
|
||||
disabled?: boolean;
|
||||
tabindex?: string;
|
||||
ariaPressed?: string;
|
||||
ariaCurrent?: string | undefined;
|
||||
}
|
||||
|
||||
export declare interface Week {
|
||||
days: Day[];
|
||||
}
|
||||
|
||||
export declare interface Month {
|
||||
weeks: Week[];
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { css, html, LitElement } from '@lion/core';
|
||||
import { LocalizeMixin } from '@lion/localize';
|
||||
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
|
|
@ -42,7 +43,7 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) {
|
|||
static get localizeNamespaces() {
|
||||
return [
|
||||
{
|
||||
'lion-calendar-overlay-frame': locale => {
|
||||
'lion-calendar-overlay-frame': /** @param {string} locale */ locale => {
|
||||
switch (locale) {
|
||||
case 'bg-BG':
|
||||
return import('@lion/overlays/translations/bg-BG.js');
|
||||
|
|
@ -94,7 +95,7 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
__dispatchCloseEvent() {
|
||||
this.dispatchEvent(new Event('close-overlay'), { bubbles: true });
|
||||
this.dispatchEvent(new Event('close-overlay'));
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { LionCalendarOverlayFrame } from './LionCalendarOverlayFrame.js';
|
|||
|
||||
/**
|
||||
* @customElement lion-input-datepicker
|
||||
* @extends {LionInputDate}
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionInputDate)) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
|
|
@ -33,19 +33,19 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
* Default will be 'suffix'.
|
||||
*/
|
||||
_calendarInvokerSlot: {
|
||||
type: String,
|
||||
attribute: false,
|
||||
},
|
||||
|
||||
__calendarMinDate: {
|
||||
type: Date,
|
||||
attribute: false,
|
||||
},
|
||||
|
||||
__calendarMaxDate: {
|
||||
type: Date,
|
||||
attribute: false,
|
||||
},
|
||||
|
||||
__calendarDisableDates: {
|
||||
type: Function,
|
||||
attribute: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -55,10 +55,14 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
...super.slots,
|
||||
[this._calendarInvokerSlot]: () => {
|
||||
const renderParent = document.createElement('div');
|
||||
this.constructor.render(this._invokerTemplate(), renderParent, {
|
||||
scopeName: this.localName,
|
||||
eventContext: this,
|
||||
});
|
||||
/** @type {typeof LionInputDatepicker} */ (this.constructor).render(
|
||||
this._invokerTemplate(),
|
||||
renderParent,
|
||||
{
|
||||
scopeName: this.localName,
|
||||
eventContext: this,
|
||||
},
|
||||
);
|
||||
return renderParent.firstElementChild;
|
||||
},
|
||||
};
|
||||
|
|
@ -67,7 +71,7 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
static get localizeNamespaces() {
|
||||
return [
|
||||
{
|
||||
'lion-input-datepicker': locale => {
|
||||
'lion-input-datepicker': /** @param {string} locale */ locale => {
|
||||
switch (locale) {
|
||||
case 'bg-BG':
|
||||
return import('../translations/bg-BG.js');
|
||||
|
|
@ -147,11 +151,13 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
}
|
||||
|
||||
get _invokerNode() {
|
||||
return this.querySelector(`#${this.__invokerId}`);
|
||||
return /** @type {HTMLElement} */ (this.querySelector(`#${this.__invokerId}`));
|
||||
}
|
||||
|
||||
get _calendarNode() {
|
||||
return this._overlayCtrl.contentNode.querySelector('[slot="content"]');
|
||||
return /** @type {LionCalendar} */ (this._overlayCtrl.contentNode.querySelector(
|
||||
'[slot="content"]',
|
||||
));
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
|
@ -172,6 +178,10 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
return `${this.localName}-${Math.random().toString(36).substr(2, 10)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
|
||||
|
|
@ -182,19 +192,19 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
|
||||
__toggleInvokerDisabled() {
|
||||
if (this._invokerNode) {
|
||||
// @ts-expect-error even though disabled may not exist on the invoker node
|
||||
// set it anyway, it doesn't harm, and is needed in case of invoker elements that do have disabled prop
|
||||
this._invokerNode.disabled = this.disabled || this.readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.__toggleInvokerDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Map} changedProperties - changed properties
|
||||
*/
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('validators')) {
|
||||
|
|
@ -241,7 +251,8 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
return html`
|
||||
<lion-calendar
|
||||
slot="content"
|
||||
.selectedDate="${this.constructor.__getSyncDownValue(this.modelValue)}"
|
||||
.selectedDate="${/** @type {typeof LionInputDatepicker} */ (this
|
||||
.constructor).__getSyncDownValue(this.modelValue)}"
|
||||
.minDate="${this.__calendarMinDate}"
|
||||
.maxDate="${this.__calendarMaxDate}"
|
||||
.disableDates="${ifDefined(this.__calendarDisableDates)}"
|
||||
|
|
@ -285,7 +296,7 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
async __openCalendarOverlay() {
|
||||
await this._overlayCtrl.show();
|
||||
await Promise.all([
|
||||
this._overlayCtrl.contentNode.updateComplete,
|
||||
/** @type {import('@lion/core').LitElement} */ (this._overlayCtrl.contentNode).updateComplete,
|
||||
this._calendarNode.updateComplete,
|
||||
]);
|
||||
this._onCalendarOverlayOpened();
|
||||
|
|
@ -304,6 +315,9 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ target: { selectedDate: Date }}} opts
|
||||
*/
|
||||
_onCalendarUserSelectedChanged({ target: { selectedDate } }) {
|
||||
if (this._hideOnUserSelect) {
|
||||
this._overlayCtrl.hide();
|
||||
|
|
@ -317,6 +331,7 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
/**
|
||||
* The LionCalendar shouldn't know anything about the modelValue;
|
||||
* it can't handle Unparseable dates, but does handle 'undefined'
|
||||
* @param {?} modelValue
|
||||
* @returns {Date|undefined} a 'guarded' modelValue
|
||||
*/
|
||||
static __getSyncDownValue(modelValue) {
|
||||
|
|
@ -326,20 +341,21 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
/**
|
||||
* Validators contain the information to synchronize the input with
|
||||
* the min, max and enabled dates of the calendar.
|
||||
* @param {Array} validators - errorValidators or warningValidators array
|
||||
* @param {import('@lion/form-core').Validator[]} validators - errorValidators or warningValidators array
|
||||
*/
|
||||
__syncDisabledDates(validators) {
|
||||
// On every validator change, synchronize disabled dates: this means
|
||||
// we need to extract minDate, maxDate, minMaxDate and disabledDates validators
|
||||
validators.forEach(v => {
|
||||
if (v.constructor.validatorName === 'MinDate') {
|
||||
const vctor = /** @type {typeof import('@lion/form-core').Validator} */ (v.constructor);
|
||||
if (vctor.validatorName === 'MinDate') {
|
||||
this.__calendarMinDate = v.param;
|
||||
} else if (v.constructor.validatorName === 'MaxDate') {
|
||||
} else if (vctor.validatorName === 'MaxDate') {
|
||||
this.__calendarMaxDate = v.param;
|
||||
} else if (v.constructor.validatorName === 'MinMaxDate') {
|
||||
} else if (vctor.validatorName === 'MinMaxDate') {
|
||||
this.__calendarMinDate = v.param.min;
|
||||
this.__calendarMaxDate = v.param.max;
|
||||
} else if (v.constructor.validatorName === 'IsDateDisabled') {
|
||||
} else if (vctor.validatorName === 'IsDateDisabled') {
|
||||
this.__calendarDisableDates = v.param;
|
||||
}
|
||||
});
|
||||
|
|
@ -359,7 +375,9 @@ export class LionInputDatepicker extends ScopedElementsMixin(OverlayMixin(LionIn
|
|||
if (this._cachedOverlayContentNode) {
|
||||
return this._cachedOverlayContentNode;
|
||||
}
|
||||
this._cachedOverlayContentNode = this.shadowRoot.querySelector('.calendar__overlay-frame');
|
||||
this._cachedOverlayContentNode = /** @type {HTMLElement} */ (
|
||||
/** @type {ShadowRoot} */ (this.shadowRoot).querySelector('.calendar__overlay-frame')
|
||||
);
|
||||
return this._cachedOverlayContentNode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { CalendarObject } from '@lion/calendar/test-helpers.js';
|
||||
|
||||
export class DatepickerInputObject {
|
||||
/** @param {import('../src/LionInputDatepicker').LionInputDatepicker} el */
|
||||
constructor(el) {
|
||||
this.el = el;
|
||||
}
|
||||
|
|
@ -27,6 +28,9 @@ export class DatepickerInputObject {
|
|||
this.overlayCloseButtonEl.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} day
|
||||
*/
|
||||
async selectMonthDay(day) {
|
||||
this.overlayController.show();
|
||||
await this.calendarEl.updateComplete;
|
||||
|
|
@ -43,19 +47,22 @@ export class DatepickerInputObject {
|
|||
}
|
||||
|
||||
get overlayEl() {
|
||||
return this.el._overlayCtrl.contentNode;
|
||||
// @ts-expect-error not supposed to call _overlayCtrl publicly here on this.el
|
||||
return /** @type {LitElement} */ (this.el._overlayCtrl.contentNode);
|
||||
}
|
||||
|
||||
get overlayHeadingEl() {
|
||||
return this.overlayEl && this.overlayEl.shadowRoot.querySelector('.calendar-overlay__heading');
|
||||
return /** @type {HTMLElement} */ (this.overlayEl &&
|
||||
this.overlayEl.shadowRoot?.querySelector('.calendar-overlay__heading'));
|
||||
}
|
||||
|
||||
get overlayCloseButtonEl() {
|
||||
return this.calendarEl && this.overlayEl.shadowRoot.querySelector('#close-button');
|
||||
return /** @type {HTMLElement} */ (this.calendarEl &&
|
||||
this.overlayEl.shadowRoot?.querySelector('#close-button'));
|
||||
}
|
||||
|
||||
get calendarEl() {
|
||||
return this.el && this.el._calendarNode;
|
||||
return /** @type {import('@lion/calendar').LionCalendar} */ (this.el && this.el._calendarNode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,6 +77,7 @@ export class DatepickerInputObject {
|
|||
*/
|
||||
|
||||
get overlayController() {
|
||||
// @ts-expect-error not supposed to call _overlayCtrl publicly here on this.el
|
||||
return this.el._overlayCtrl;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ const tagString = 'lion-input-datepicker';
|
|||
describe('<lion-input-datepicker> integrations', () => {
|
||||
runInteractionStateMixinSuite({
|
||||
tagString,
|
||||
suffix: tagString,
|
||||
allowedModelValueTypes: [Date],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ import { LionCalendar } from '@lion/calendar';
|
|||
import { isSameDate } from '@lion/calendar/src/utils/isSameDate.js';
|
||||
import { html, LitElement } from '@lion/core';
|
||||
import { IsDateDisabled, MaxDate, MinDate, MinMaxDate } from '@lion/form-core';
|
||||
import { aTimeout, defineCE, expect, fixture, nextFrame } from '@open-wc/testing';
|
||||
import { aTimeout, defineCE, expect, fixture as _fixture, nextFrame } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import '../lion-input-datepicker.js';
|
||||
import { LionInputDatepicker } from '../src/LionInputDatepicker.js';
|
||||
import { DatepickerInputObject } from '../test-helpers.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionInputDatepicker>} */ (_fixture);
|
||||
|
||||
describe('<lion-input-datepicker>', () => {
|
||||
describe('Calendar Overlay', () => {
|
||||
it('implements calendar-overlay Style component', async () => {
|
||||
|
|
@ -46,7 +52,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(
|
||||
elObj.overlayHeadingEl.querySelector('slot[name="heading"]').assignedNodes()[0],
|
||||
/** @type {HTMLSlotElement} */ (elObj.overlayHeadingEl.querySelector(
|
||||
'slot[name="heading"]',
|
||||
)).assignedNodes()[0],
|
||||
).lightDom.to.equal('Pick your date');
|
||||
});
|
||||
|
||||
|
|
@ -60,7 +68,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(
|
||||
elObj.overlayHeadingEl.querySelector('slot[name="heading"]').assignedNodes()[0],
|
||||
/** @type {HTMLSlotElement} */ (elObj.overlayHeadingEl.querySelector(
|
||||
'slot[name="heading"]',
|
||||
)).assignedNodes()[0],
|
||||
).lightDom.to.equal('foo');
|
||||
});
|
||||
|
||||
|
|
@ -95,7 +105,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
expect(elObj.overlayController.isShown).to.equal(true);
|
||||
|
||||
document.body.click();
|
||||
await aTimeout();
|
||||
await aTimeout(0);
|
||||
expect(elObj.overlayController.isShown).to.be.false;
|
||||
});
|
||||
|
||||
|
|
@ -149,7 +159,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
await elObj.openCalendar();
|
||||
expect(elObj.calendarEl.selectedDate).to.equal(myDate);
|
||||
await elObj.selectMonthDay(myOtherDate.getDate());
|
||||
expect(isSameDate(el.modelValue, myOtherDate)).to.be.true;
|
||||
expect(isSameDate(/** @type {Date} */ (el.modelValue), myOtherDate)).to.be.true;
|
||||
});
|
||||
|
||||
it('closes the calendar overlay on "user-selected-date-changed"', async () => {
|
||||
|
|
@ -169,16 +179,26 @@ describe('<lion-input-datepicker>', () => {
|
|||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
await aTimeout();
|
||||
expect(isSameDate(elObj.calendarEl.focusedDate, elObj.calendarEl.selectedDate)).to.be.true;
|
||||
await aTimeout(0);
|
||||
expect(
|
||||
isSameDate(
|
||||
/** @type {Date} */ (elObj.calendarEl.focusedDate),
|
||||
/** @type {Date} */ (elObj.calendarEl.selectedDate),
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
it('focuses central date on opening of calendar if no date selected', async () => {
|
||||
const el = await fixture(html`<lion-input-datepicker></lion-input-datepicker>`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
await aTimeout();
|
||||
expect(isSameDate(elObj.calendarEl.focusedDate, elObj.calendarEl.centralDate)).to.be.true;
|
||||
await aTimeout(0);
|
||||
expect(
|
||||
isSameDate(
|
||||
/** @type {Date} */ (elObj.calendarEl.focusedDate),
|
||||
elObj.calendarEl.centralDate,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
describe('Validators', () => {
|
||||
|
|
@ -189,9 +209,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
* lion-calendar
|
||||
*/
|
||||
it('converts IsDateDisabled validator to "disableDates" property', async () => {
|
||||
const no15th = d => d.getDate() !== 15;
|
||||
const no16th = d => d.getDate() !== 16;
|
||||
const no15thOr16th = d => no15th(d) && no16th(d);
|
||||
const no15th = /** @param {Date} d */ d => d.getDate() !== 15;
|
||||
const no16th = /** @param {Date} d */ d => d.getDate() !== 16;
|
||||
const no15thOr16th = /** @param {Date} d */ d => no15th(d) && no16th(d);
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .validators="${[new IsDateDisabled(no15thOr16th)]}">
|
||||
</lion-input-datepicker>
|
||||
|
|
@ -204,9 +224,10 @@ describe('<lion-input-datepicker>', () => {
|
|||
|
||||
it('converts MinDate validator to "minDate" property', async () => {
|
||||
const myMinDate = new Date('2019/06/15');
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .validators="${[new MinDate(myMinDate)]}">
|
||||
</lion-input-date>`);
|
||||
const el = await fixture(html` <lion-input-datepicker
|
||||
.validators="${[new MinDate(myMinDate)]}"
|
||||
>
|
||||
</lion-input-datepicker>`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
|
|
@ -309,7 +330,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
});
|
||||
|
||||
it('is accessible with a disabled date', async () => {
|
||||
const no15th = d => d.getDate() !== 15;
|
||||
const no15th = /** @param {Date} d */ d => d.getDate() !== 15;
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .validators=${[new IsDateDisabled(no15th)]}> </lion-input-datepicker>
|
||||
`);
|
||||
|
|
@ -333,7 +354,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myEl = await fixture(html`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
expect(myElObj.invokerEl.tagName.toLowerCase()).to.equal('my-button');
|
||||
|
||||
|
|
@ -363,7 +384,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myEl = await fixture(html`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
expect(myElObj.invokerEl.getAttribute('slot')).to.equal('prefix');
|
||||
});
|
||||
|
|
@ -391,7 +412,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myEl = await fixture(html`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
|
||||
// All other tests will still pass. Small checkup:
|
||||
|
|
@ -434,26 +455,28 @@ describe('<lion-input-datepicker>', () => {
|
|||
return html`
|
||||
<my-calendar-overlay-frame id="calendar-overlay">
|
||||
<span slot="heading">${this.calendarHeading}</span>
|
||||
${this._calendarTemplateConfig(this._calendarTemplate())}
|
||||
</my-calendar-overlay-frame>
|
||||
`;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onCalendarOverlayOpened(...args) {
|
||||
super._onCalendarOverlayOpened(...args);
|
||||
_onCalendarOverlayOpened() {
|
||||
super._onCalendarOverlayOpened();
|
||||
myOverlayOpenedCbHandled = true;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onCalendarUserSelectedChanged(...args) {
|
||||
super._onCalendarUserSelectedChanged(...args);
|
||||
/**
|
||||
* @override
|
||||
* @param {{ target: { selectedDate: Date }}} opts
|
||||
*/
|
||||
_onCalendarUserSelectedChanged({ target: { selectedDate } }) {
|
||||
super._onCalendarUserSelectedChanged({ target: { selectedDate } });
|
||||
myUserSelectedChangedCbHandled = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myEl = await fixture(html`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
|
||||
// All other tests will still pass. Small checkup:
|
||||
|
|
@ -476,7 +499,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
<lion-input-datepicker></lion-input-datepicker>
|
||||
</form>
|
||||
`);
|
||||
const el = form.children[0];
|
||||
const el = /** @type {LionInputDatepicker} */ (form.children[0]);
|
||||
await el.updateComplete;
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"include": [
|
||||
"packages/accordion/**/*.js",
|
||||
"packages/button/src/**/*.js",
|
||||
"packages/calendar/**/*.js",
|
||||
"packages/checkbox-group/**/*.js",
|
||||
"packages/collapsible/**/*.js",
|
||||
"packages/core/**/*.js",
|
||||
|
|
@ -27,6 +28,7 @@
|
|||
"packages/input/**/*.js",
|
||||
"packages/input-amount/**/*.js",
|
||||
"packages/input-date/**/*.js",
|
||||
"packages/input-datepicker/**/*.js",
|
||||
"packages/input-email/**/*.js",
|
||||
"packages/input-iban/**/*.js",
|
||||
"packages/input-range/**/*.js",
|
||||
|
|
|
|||
Loading…
Reference in a new issue