diff --git a/packages/calendar/src/LionCalendar.js b/packages/calendar/src/LionCalendar.js index 858690abe..34acec374 100644 --- a/packages/calendar/src/LionCalendar.js +++ b/packages/calendar/src/LionCalendar.js @@ -156,7 +156,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { render() { return html`
- ${this.__renderHeader()} ${this.__renderData()} + ${this.__renderNavigation()} ${this.__renderData()}
`; } @@ -173,6 +173,14 @@ export class LionCalendar extends LocalizeMixin(LitElement) { this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month', mode: 'both' }); } + goToNextYear() { + this.__modifyDate(1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' }); + } + + goToPreviousYear() { + this.__modifyDate(-1, { dateType: 'centralDate', type: 'FullYear', mode: 'both' }); + } + async focusDate(date) { this.centralDate = date; await this.updateComplete; @@ -254,9 +262,7 @@ export class LionCalendar extends LocalizeMixin(LitElement) { } } - __renderHeader() { - const month = getMonthNames({ locale: this.__getLocale() })[this.centralDate.getMonth()]; - const year = this.centralDate.getFullYear(); + __renderMonthNavigation(month, year) { const nextMonth = this.centralDate.getMonth() === 11 ? getMonthNames({ locale: this.__getLocale() })[0] @@ -265,22 +271,40 @@ export class LionCalendar extends LocalizeMixin(LitElement) { this.centralDate.getMonth() === 0 ? getMonthNames({ locale: this.__getLocale() })[11] : getMonthNames({ locale: this.__getLocale() })[this.centralDate.getMonth() - 1]; - const nextYear = - this.centralDate.getMonth() === 11 - ? this.centralDate.getFullYear() + 1 - : this.centralDate.getFullYear(); - const previousYear = - this.centralDate.getMonth() === 0 - ? this.centralDate.getFullYear() - 1 - : this.centralDate.getFullYear(); + const nextYear = this.centralDate.getMonth() === 11 ? year + 1 : year; + const previousYear = this.centralDate.getMonth() === 0 ? year - 1 : year; + return html` +
+ ${this.__renderPreviousButton('Month', previousMonth, previousYear)} +

+ ${month} +

+ ${this.__renderNextButton('Month', nextMonth, nextYear)} +
+ `; + } + + __renderYearNavigation(month, year) { + const nextYear = year + 1; + const previousYear = year - 1; return html` -
- ${this.__renderPreviousButton(previousMonth, previousYear)} -

- ${month} ${year} +
+ ${this.__renderPreviousButton('FullYear', month, previousYear)} +

+ ${year}

- ${this.__renderNextButton(nextMonth, nextYear)} + ${this.__renderNextButton('FullYear', month, nextYear)} +
+ `; + } + + __renderNavigation() { + const month = getMonthNames({ locale: this.__getLocale() })[this.centralDate.getMonth()]; + const year = this.centralDate.getFullYear(); + return html` +
+ ${this.__renderYearNavigation(month, year)} ${this.__renderMonthNavigation(month, year)}
`; } @@ -302,40 +326,96 @@ export class LionCalendar extends LocalizeMixin(LitElement) { }); } - __renderPreviousButton(previousMonth, previousYear) { - const previousButtonTitle = `${this.msgLit( - 'lion-calendar:previousMonth', - )}, ${previousMonth} ${previousYear}`; + __getPreviousDisabled(type, previousMonth, previousYear) { + let disabled; + let month = previousMonth; + if (this.minDate && type === 'Month') { + disabled = getLastDayPreviousMonth(this.centralDate) < this.minDate; + } else if (this.minDate) { + disabled = previousYear < this.minDate.getFullYear(); + } + if (!disabled && this.minDate && type === 'FullYear') { + // change the month to the first available month + if (previousYear === this.minDate.getFullYear()) { + if (this.centralDate.getMonth() < this.minDate.getMonth()) { + month = getMonthNames({ locale: this.__getLocale() })[this.minDate.getMonth()]; + } + } + } + return { disabled, month }; + } + + __getNextDisabled(type, nextMonth, nextYear) { + let disabled; + let month = nextMonth; + if (this.maxDate && type === 'Month') { + disabled = getFirstDayNextMonth(this.centralDate) > this.maxDate; + } else if (this.maxDate) { + disabled = nextYear > this.maxDate.getFullYear(); + } + if (!disabled && this.maxDate && type === 'FullYear') { + // change the month to the first available month + if (nextYear === this.maxDate.getFullYear()) { + if (this.centralDate.getMonth() >= this.maxDate.getMonth()) { + month = getMonthNames({ locale: this.__getLocale() })[this.maxDate.getMonth()]; + } + } + } + return { disabled, month }; + } + + __renderPreviousButton(type, previousMonth, previousYear) { + const { disabled, month } = this.__getPreviousDisabled(type, previousMonth, previousYear); + const previousButtonTitle = this.__getNavigationLabel('previous', type, month, previousYear); + function clickDateDelegation() { + if (type === 'FullYear') { + this.goToPreviousYear(); + } else { + this.goToPreviousMonth(); + } + } return html` `; } - __renderNextButton(nextMonth, nextYear) { - const nextButtonTitle = `${this.msgLit('lion-calendar:nextMonth')}, ${nextMonth} ${nextYear}`; + __renderNextButton(type, nextMonth, nextYear) { + const { disabled, month } = this.__getNextDisabled(type, nextMonth, nextYear); + const nextButtonTitle = this.__getNavigationLabel('next', type, month, nextYear); + function clickDateDelegation() { + if (type === 'FullYear') { + this.goToNextYear(); + } else { + this.goToNextMonth(); + } + } return html` `; } + __getNavigationLabel(mode, type, month, year) { + return `${this.msgLit(`lion-calendar:${mode}${type}`)}, ${month} ${year}`; + } + __coreDayPreprocessor(_day, { currentMonth = false } = {}) { const day = createDay(new Date(_day.date), _day); const today = normalizeDateTime(new Date()); @@ -380,12 +460,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) { }); }); }); - - this.isNextMonthDisabled = - this.maxDate && getFirstDayNextMonth(this.centralDate) > this.maxDate; - this.isPreviousMonthDisabled = - this.minDate && getLastDayPreviousMonth(this.centralDate) < this.minDate; - return data; } diff --git a/packages/calendar/src/calendarStyle.js b/packages/calendar/src/calendarStyle.js index d809581e0..efd75d593 100644 --- a/packages/calendar/src/calendarStyle.js +++ b/packages/calendar/src/calendarStyle.js @@ -13,19 +13,21 @@ export const calendarStyle = css` display: block; } - .calendar__header { - display: flex; - justify-content: space-between; - border-bottom: 1px solid #adadad; + .calendar__navigation { padding: 0 8px; } - .calendar__month-heading { + .calendar__navigation__month, + .calendar__navigation__year { + display: flex; + } + + .calendar__navigation-heading { margin: 0.5em 0; } - .calendar__previous-month-button, - .calendar__next-month-button { + .calendar__previous-button, + .calendar__next-button { background-color: #fff; border: 0; padding: 0; diff --git a/packages/calendar/src/utils/dataTemplate.js b/packages/calendar/src/utils/dataTemplate.js index 12d975379..7d2b2cdfe 100644 --- a/packages/calendar/src/utils/dataTemplate.js +++ b/packages/calendar/src/utils/dataTemplate.js @@ -14,7 +14,7 @@ export function dataTemplate( data-wrap-cols aria-readonly="true" class="calendar__grid" - aria-labelledby="month_and_year" + aria-labelledby="month year" > diff --git a/packages/calendar/src/utils/dayTemplate.js b/packages/calendar/src/utils/dayTemplate.js index d7442744f..65f42c2ae 100644 --- a/packages/calendar/src/utils/dayTemplate.js +++ b/packages/calendar/src/utils/dayTemplate.js @@ -14,14 +14,43 @@ const defaultMonthLabels = [ 'November', 'December', ]; +const firstWeekDays = [1, 2, 3, 4, 5, 6, 7]; +const lastDaysOfYear = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 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 firstDay = dayNumber === 1; + const endOfFirstWeek = day.weekOrder === 6 && firstWeekDays.includes(dayNumber); + const startOfFirstFullWeek = day.startOfWeek && firstWeekDays.includes(dayNumber); + + if (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)) { + lastDaysOfYear[1] = 29; + } + const lastDayNumber = lastDaysOfYear[day.date.getMonth()]; + const lastWeekDays = []; + for (let i = lastDayNumber; i >= lastDayNumber - 7; i -= 1) { + lastWeekDays.push(i); + } + const endOfLastFullWeek = day.weekOrder === 6 && lastWeekDays.includes(dayNumber); + const startOfLastWeek = day.startOfWeek && lastWeekDays.includes(dayNumber); + const lastDay = lastDayNumber === dayNumber; + return html` - + `; diff --git a/packages/calendar/test-helpers/CalendarObject.js b/packages/calendar/test-helpers/CalendarObject.js index 36882e209..0d47ce0ef 100644 --- a/packages/calendar/test-helpers/CalendarObject.js +++ b/packages/calendar/test-helpers/CalendarObject.js @@ -18,19 +18,31 @@ export class CalendarObject { } get headerEl() { - return this.el.shadowRoot.querySelector('.calendar__header'); + return this.el.shadowRoot.querySelector('.calendar__navigation'); + } + + get yearHeadingEl() { + return this.el.shadowRoot.querySelector('#year'); } get monthHeadingEl() { - return this.el.shadowRoot.querySelector('.calendar__month-heading'); + return this.el.shadowRoot.querySelector('#month'); + } + + get nextYearButtonEl() { + return this.el.shadowRoot.querySelectorAll('.calendar__next-button')[0]; + } + + get previousYearButtonEl() { + return this.el.shadowRoot.querySelectorAll('.calendar__previous-button')[0]; } get nextMonthButtonEl() { - return this.el.shadowRoot.querySelector('.calendar__next-month-button'); + return this.el.shadowRoot.querySelectorAll('.calendar__next-button')[1]; } get previousMonthButtonEl() { - return this.el.shadowRoot.querySelector('.calendar__previous-month-button'); + return this.el.shadowRoot.querySelectorAll('.calendar__previous-button')[1]; } get gridEl() { @@ -117,15 +129,11 @@ export class CalendarObject { /** * States */ - get activeMonthAndYear() { + get activeMonth() { return this.monthHeadingEl.textContent.trim(); } - get activeMonth() { - return this.activeMonthAndYear.split(' ')[0]; - } - get activeYear() { - return this.activeMonthAndYear.split(' ')[1]; + return this.yearHeadingEl.textContent.trim(); } } diff --git a/packages/calendar/test/lion-calendar.test.js b/packages/calendar/test/lion-calendar.test.js index d86c404e4..e65bb7a32 100644 --- a/packages/calendar/test/lion-calendar.test.js +++ b/packages/calendar/test/lion-calendar.test.js @@ -18,10 +18,10 @@ describe('', () => { const el = await fixture(html``); expect(el.shadowRoot.querySelector('.calendar')).to.exist; - expect(el.shadowRoot.querySelector('.calendar__header')).to.exist; - expect(el.shadowRoot.querySelector('.calendar__previous-month-button')).to.exist; - expect(el.shadowRoot.querySelector('.calendar__next-month-button')).to.exist; - expect(el.shadowRoot.querySelector('.calendar__month-heading')).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; }); @@ -30,25 +30,52 @@ describe('', () => { const el = await fixture(html``); - expect(el.shadowRoot.querySelector('.calendar__month-heading')).dom.to.equal(` + expect(el.shadowRoot.querySelector('#year')).dom.to.equal(`

- December 2000 + 2000

`); + expect(el.shadowRoot.querySelector('#month')).dom.to.equal(` +

+ December +

+ `); clock.restore(); }); + it('has previous year button', async () => { + const el = await fixture( + html``, + ); + expect(el.shadowRoot.querySelectorAll('.calendar__previous-button')[0]).dom.to.equal(` + + `); + }); + + it('has next year button', async () => { + const el = await fixture( + html``, + ); + expect(el.shadowRoot.querySelectorAll('.calendar__next-button')[0]).dom.to.equal(` + + `); + }); + it('has previous month button', async () => { const el = await fixture( html``, ); - expect(el.shadowRoot.querySelector('.calendar__previous-month-button')).dom.to.equal(` - + expect(el.shadowRoot.querySelectorAll('.calendar__previous-button')[1]).dom.to.equal(` + `); }); @@ -56,8 +83,8 @@ describe('', () => { const el = await fixture( html``, ); - expect(el.shadowRoot.querySelector('.calendar__next-month-button')).dom.to.equal(` - + expect(el.shadowRoot.querySelectorAll('.calendar__next-button')[1]).dom.to.equal(` + `); }); }); @@ -69,7 +96,8 @@ describe('', () => { `), ); - expect(elObj.activeMonthAndYear).to.equal('May 2014'); + expect(elObj.activeMonth).to.equal('May'); + expect(elObj.activeYear).to.equal('2014'); }); it('sets "centralDate" to today by default', async () => { @@ -78,7 +106,8 @@ describe('', () => { const el = await fixture(html``); const elObj = new CalendarObject(el); expect(isSameDate(el.centralDate, new Date())).to.be.true; - expect(elObj.activeMonthAndYear).to.equal('March 2013'); + expect(elObj.activeMonth).to.equal('March'); + expect(elObj.activeYear).to.equal('2013'); clock.restore(); }); @@ -125,7 +154,8 @@ describe('', () => { > `), ); - expect(elObj.activeMonthAndYear).to.equal('June 2018'); + expect(elObj.activeMonth).to.equal('June'); + expect(elObj.activeYear).to.equal('2018'); }); it('changes "centralDate" from default to "selectedDate" on first render if no other custom "centralDate" is provided', async () => { @@ -136,8 +166,8 @@ describe('', () => { `); const elObj = new CalendarObject(el); expect(isSameDate(el.centralDate, new Date('2016/10/20'))).to.be.true; - expect(elObj.activeMonthAndYear).to.equal('October 2016'); - + expect(elObj.activeMonth).to.equal('October'); + expect(elObj.activeYear).to.equal('2016'); clock.restore(); }); @@ -318,7 +348,8 @@ describe('', () => { `); const elObj = new CalendarObject(el); expect(isSameDate(el.centralDate, new Date('2001/01/08'))).to.be.true; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); clock.restore(); }); @@ -377,14 +408,15 @@ describe('', () => { }); }); - describe('Calendar header (month navigation)', () => { + describe('Calendar navigation', () => { describe('Title', () => { it('contains secondary title displaying the current month and year in focus', async () => { const el = await fixture( html``, ); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); }); it('updates the secondary title when the displayed month/year changes', async () => { @@ -394,7 +426,8 @@ describe('', () => { const elObj = new CalendarObject(el); el.centralDate = new Date('1999/10/12'); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('October 1999'); + expect(elObj.activeMonth).to.equal('October'); + expect(elObj.activeYear).to.equal('1999'); }); describe('Accessibility', () => { @@ -406,130 +439,235 @@ describe('', () => { }); describe('Navigation', () => { - it('has a button for navigation to previous month', async () => { - const el = await fixture( - html``, - ); - const elObj = new CalendarObject(el); - expect(elObj.previousMonthButtonEl).not.to.equal(null); - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + describe('Year', () => { + it('has a button for navigation to previous year', async () => { + const el = await fixture( + html``, + ); + const elObj = new CalendarObject(el); + expect(elObj.previousYearButtonEl).not.to.equal(null); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); - elObj.previousMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + elObj.previousYearButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2000'); - elObj.previousMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('November 2000'); + elObj.previousYearButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('1999'); + }); + + it('has a button for navigation to next year', async () => { + const el = await fixture( + html``, + ); + const elObj = new CalendarObject(el); + expect(elObj.nextYearButtonEl).not.to.equal(null); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); + + elObj.nextYearButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2001'); + + elObj.nextYearButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2002'); + }); + + it('disables previousYearButton and nextYearButton based on disabled days accordingly', async () => { + const el = await fixture(html` + + `); + 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); + + 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); + + elObj.previousYearButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('June'); + expect(elObj.activeYear).to.equal('2000'); + + el.minDate = new Date('1999/12/31'); + 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); + }); + + it('sets label to correct month previousYearButton and nextYearButton based on disabled days accordingly', async () => { + const el = await fixture(html` + + `); + const elObj = new CalendarObject(el); + el.minDate = new Date('1999/07/12'); + el.maxDate = new Date('2001/05/12'); + await el.updateComplete; + + 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.ariaLabel).to.equal('Next year, May 2001'); + }); }); - it('has a button for navigation to next month', async () => { - const el = await fixture( - html``, - ); - const elObj = new CalendarObject(el); - expect(elObj.nextMonthButtonEl).not.to.equal(null); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + describe('Month', () => { + it('has a button for navigation to previous month', async () => { + const el = await fixture( + html``, + ); + const elObj = new CalendarObject(el); + expect(elObj.previousMonthButtonEl).not.to.equal(null); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); - elObj.nextMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + elObj.previousMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); - elObj.nextMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('February 2001'); - }); + elObj.previousMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('November'); + expect(elObj.activeYear).to.equal('2000'); + }); - it('disables previousMonthButton and nextMonthButton based on disabled days accordingly', async () => { - const el = await fixture(html` - - `); - const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); - expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(false); - expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(false); + it('has a button for navigation to next month', async () => { + const el = await fixture( + html``, + ); + const elObj = new CalendarObject(el); + expect(elObj.nextMonthButtonEl).not.to.equal(null); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); - el.minDate = new Date('2000/12/01'); - el.maxDate = new Date('2000/12/31'); - await el.updateComplete; + elObj.nextMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); - expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(true); - expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(true); + elObj.nextMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('February'); + expect(elObj.activeYear).to.equal('2001'); + }); - elObj.previousMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + it('disables previousMonthButton and nextMonthButton based on disabled days accordingly', async () => { + const el = await fixture(html` + + `); + 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); - elObj.previousMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('December 2000'); - }); + el.minDate = new Date('2000/12/01'); + el.maxDate = new Date('2000/12/31'); + await el.updateComplete; - it('handles switch to previous month when dates are disabled', async () => { - const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() }); + expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(true); + expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(true); - const el = await fixture(html``); - const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + elObj.previousMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); - el.minDate = new Date('2000/11/20'); - await el.updateComplete; + elObj.previousMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); + }); - expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(false); - expect(isSameDate(el.centralDate, new Date('2000/12/15'))).to.be.true; + it('handles switch to previous month when dates are disabled', async () => { + const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() }); - elObj.previousMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('November 2000'); - expect(isSameDate(el.centralDate, new Date('2000/11/20'))).to.be.true; + const el = await fixture(html``); + const elObj = new CalendarObject(el); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); - clock.restore(); - }); + el.minDate = new Date('2000/11/20'); + await el.updateComplete; - it('handles switch to next month when dates are disabled', async () => { - const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() }); + expect(elObj.previousMonthButtonEl.hasAttribute('disabled')).to.equal(false); + expect(isSameDate(el.centralDate, new Date('2000/12/15'))).to.be.true; - const el = await fixture(html``); - const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + elObj.previousMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('November'); + expect(elObj.activeYear).to.equal('2000'); + expect(isSameDate(el.centralDate, new Date('2000/11/20'))).to.be.true; - el.maxDate = new Date('2001/01/10'); - await el.updateComplete; + clock.restore(); + }); - expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(false); - expect(isSameDate(el.centralDate, new Date('2000/12/15'))).to.be.true; + it('handles switch to next month when dates are disabled', async () => { + const clock = sinon.useFakeTimers({ now: new Date('2000/12/15').getTime() }); - elObj.nextMonthButtonEl.click(); - await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); - expect(isSameDate(el.centralDate, new Date('2001/01/10'))).to.be.true; + const el = await fixture(html``); + const elObj = new CalendarObject(el); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); - clock.restore(); - }); + el.maxDate = new Date('2001/01/10'); + await el.updateComplete; - it('supports navigating from larger months to smaller ones (day counts)', async () => { - // given - const inputDate = new Date('2019/08/31'); - const element = await fixture(html` - - `); - // when - const remote = new CalendarObject(element); - remote.nextMonthButtonEl.click(); - await element.updateComplete; - // then - expect(remote.activeMonthAndYear).to.equal('September 2019'); - expect(remote.centralDayObj.el).dom.to.equal(` - - `); + expect(elObj.nextMonthButtonEl.hasAttribute('disabled')).to.equal(false); + expect(isSameDate(el.centralDate, new Date('2000/12/15'))).to.be.true; + + elObj.nextMonthButtonEl.click(); + await el.updateComplete; + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); + expect(isSameDate(el.centralDate, new Date('2001/01/10'))).to.be.true; + + clock.restore(); + }); + + it('supports navigating from larger months to smaller ones (day counts)', async () => { + // given + const inputDate = new Date('2019/08/31'); + const element = await fixture(html` + + `); + // when + const remote = new CalendarObject(element); + 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(` + + `); + }); }); describe('Accessibility', () => { @@ -538,6 +676,7 @@ describe('', () => { `); const elObj = new CalendarObject(el); + // month expect(elObj.previousMonthButtonEl.getAttribute('title')).to.equal( 'Previous month, November 2000', ); @@ -550,6 +689,18 @@ describe('', () => { expect(elObj.nextMonthButtonEl.getAttribute('aria-label')).to.equal( 'Next month, January 2001', ); + + // year + expect(elObj.previousYearButtonEl.getAttribute('title')).to.equal( + 'Previous year, December 1999', + ); + expect(elObj.previousYearButtonEl.getAttribute('aria-label')).to.equal( + 'Previous year, December 1999', + ); + expect(elObj.nextYearButtonEl.getAttribute('title')).to.equal('Next year, December 2001'); + expect(elObj.nextYearButtonEl.getAttribute('aria-label')).to.equal( + 'Next year, December 2001', + ); }); }); }); @@ -670,17 +821,20 @@ describe('', () => { `); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); el.__contentWrapperElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'PageUp' })); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'PageDown' }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); }); it('navigates through years with [alt + pageup] [alt + pagedown] keys', async () => { @@ -688,19 +842,22 @@ describe('', () => { `); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'PageDown', altKey: true }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2002'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2002'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'PageUp', altKey: true }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); }); describe('Arrow keys', () => { @@ -809,13 +966,15 @@ describe('', () => { `); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'ArrowRight' }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); expect(elObj.focusedDayObj.monthday).to.equal(1); }); @@ -824,13 +983,15 @@ describe('', () => { `); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'ArrowLeft' }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); expect(elObj.focusedDayObj.monthday).to.equal(31); }); @@ -839,13 +1000,15 @@ describe('', () => { `); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'ArrowDown' }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); expect(elObj.focusedDayObj.monthday).to.equal(6); }); @@ -854,13 +1017,15 @@ describe('', () => { `); const elObj = new CalendarObject(el); - expect(elObj.activeMonthAndYear).to.equal('January 2001'); + expect(elObj.activeMonth).to.equal('January'); + expect(elObj.activeYear).to.equal('2001'); el.__contentWrapperElement.dispatchEvent( new KeyboardEvent('keydown', { key: 'ArrowUp' }), ); await el.updateComplete; - expect(elObj.activeMonthAndYear).to.equal('December 2000'); + expect(elObj.activeMonth).to.equal('December'); + expect(elObj.activeYear).to.equal('2000'); expect(elObj.focusedDayObj.monthday).to.equal(26); }); }); diff --git a/packages/calendar/test/utils/dayTemplate.test.js b/packages/calendar/test/utils/dayTemplate.test.js index bcf38386d..47387baab 100644 --- a/packages/calendar/test/utils/dayTemplate.test.js +++ b/packages/calendar/test/utils/dayTemplate.test.js @@ -1,17 +1,14 @@ /* eslint-disable no-unused-expressions */ import { expect, fixture } from '@open-wc/testing'; - import { createDay } from '../../src/utils/createDay.js'; import { dayTemplate } from '../../src/utils/dayTemplate.js'; +const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + describe('dayTemplate', () => { it('renders day cell', async () => { const day = createDay(new Date('2019/04/19'), { weekOrder: 5 }); - const el = await fixture( - dayTemplate(day, { - weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - }), - ); + const el = await fixture(dayTemplate(day, { weekdays })); expect(el).dom.to.equal(` `); }); + + it('sets the first-day attribute', async () => { + let day = createDay(new Date('2019/04/01')); + const el1 = await fixture(dayTemplate(day, { weekdays })); + expect(el1.hasAttribute('first-day')).to.be.true; + day = createDay(new Date('2019/04/02')); + const el2 = await fixture(dayTemplate(day, { weekdays })); + expect(el2.hasAttribute('first-day')).to.be.false; + }); + + it('sets the end-of-first-week attribute', async () => { + let day = createDay(new Date('2019/04/06'), { weekOrder: 6 }); + const el1 = await fixture(dayTemplate(day, { weekdays })); + expect(el1.hasAttribute('end-of-first-week')).to.be.true; + day = createDay(new Date('2019/04/13'), { weekOrder: 6 }); + const el2 = await fixture(dayTemplate(day, { weekdays })); + expect(el2.hasAttribute('end-of-first-week')).to.be.false; + }); + + it('sets the start-of-first-full-week attribute', async () => { + let day = createDay(new Date('2019/04/07'), { startOfWeek: true }); + const el1 = await fixture(dayTemplate(day, { weekdays })); + expect(el1.hasAttribute('start-of-first-full-week')).to.be.true; + day = createDay(new Date('2019/04/14'), { startOfWeek: true }); + const el2 = await fixture(dayTemplate(day, { weekdays })); + expect(el2.hasAttribute('start-of-first-full-week')).to.be.false; + }); + + it('sets the end-of-last-full-week attribute', async () => { + let day = createDay(new Date('2019/04/27'), { weekOrder: 6 }); + const el1 = await fixture(dayTemplate(day, { weekdays })); + expect(el1.hasAttribute('end-of-last-full-week')).to.be.true; + day = createDay(new Date('2019/04/20'), { weekOrder: 6 }); + const el2 = await fixture(dayTemplate(day, { weekdays })); + expect(el2.hasAttribute('end-of-last-full-week')).to.be.false; + }); + + it('sets the start-of-last-week attribute', async () => { + let day = createDay(new Date('2019/04/28'), { startOfWeek: true }); + const el1 = await fixture(dayTemplate(day, { weekdays })); + expect(el1.hasAttribute('start-of-last-week')).to.be.true; + day = createDay(new Date('2019/04/21'), { startOfWeek: true }); + const el2 = await fixture(dayTemplate(day, { weekdays })); + expect(el2.hasAttribute('start-of-last-week')).to.be.false; + }); + + it('sets the last-day attribute', async () => { + let day = createDay(new Date('2019/04/30'), { startOfWeek: true }); + const el1 = await fixture(dayTemplate(day, { weekdays })); + expect(el1.hasAttribute('last-day')).to.be.true; + day = createDay(new Date('2019/03/30'), { startOfWeek: true }); + const el2 = await fixture(dayTemplate(day, { weekdays })); + expect(el2.hasAttribute('last-day')).to.be.false; + day = createDay(new Date('2019/02/28'), { startOfWeek: true }); + const el3 = await fixture(dayTemplate(day, { weekdays })); + expect(el3.hasAttribute('last-day')).to.be.true; + }); }); diff --git a/packages/calendar/test/utils/snapshots/monthTemplate_en-GB_Sunday_2018-12.js b/packages/calendar/test/utils/snapshots/monthTemplate_en-GB_Sunday_2018-12.js index 137d3c97d..77fae102e 100644 --- a/packages/calendar/test/utils/snapshots/monthTemplate_en-GB_Sunday_2018-12.js +++ b/packages/calendar/test/utils/snapshots/monthTemplate_en-GB_Sunday_2018-12.js @@ -3,7 +3,7 @@ const html = strings => strings[0]; export default html`
- - - - @@ -202,7 +202,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 9 + 9 @@ -274,7 +274,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 16 + 16 @@ -346,7 +346,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 23 + 23 - - - - - diff --git a/packages/calendar/translations/bg.js b/packages/calendar/translations/bg.js index 98e5924bf..64a60be87 100644 --- a/packages/calendar/translations/bg.js +++ b/packages/calendar/translations/bg.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Следващ месец', previousMonth: 'Предишен месец', + nextFullYear: 'Следващ година', + previousFullYear: 'Предишен година', }; diff --git a/packages/calendar/translations/cs.js b/packages/calendar/translations/cs.js index a3816b7e7..2823148c7 100644 --- a/packages/calendar/translations/cs.js +++ b/packages/calendar/translations/cs.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Příští měsíc', previousMonth: 'Předchozí měsíc', + nextFullYear: 'Příští rok', + previousFullYear: 'Předchozí rok', }; diff --git a/packages/calendar/translations/de.js b/packages/calendar/translations/de.js index 8dfe0c87a..46743c422 100644 --- a/packages/calendar/translations/de.js +++ b/packages/calendar/translations/de.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Nächster Monat', previousMonth: 'Vorheriger Monat', + nextFullYear: 'Nächster Jahr', + previousFullYear: 'Vorheriger Jahr', }; diff --git a/packages/calendar/translations/en.js b/packages/calendar/translations/en.js index 46fc92a75..f373d56f0 100644 --- a/packages/calendar/translations/en.js +++ b/packages/calendar/translations/en.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Next month', previousMonth: 'Previous month', + nextFullYear: 'Next year', + previousFullYear: 'Previous year', }; diff --git a/packages/calendar/translations/es.js b/packages/calendar/translations/es.js index 35961daa8..be79a6ead 100644 --- a/packages/calendar/translations/es.js +++ b/packages/calendar/translations/es.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Mes siguiente', previousMonth: 'Mes anterior', + nextFullYear: 'Año siguiente', + previousFullYear: 'Año anterior', }; diff --git a/packages/calendar/translations/fr.js b/packages/calendar/translations/fr.js index af7112ac9..da67c3f56 100644 --- a/packages/calendar/translations/fr.js +++ b/packages/calendar/translations/fr.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Mois prochain', previousMonth: 'Mois précédent', + nextFullYear: 'An prochain', + previousFullYear: 'An précédent', }; diff --git a/packages/calendar/translations/hu.js b/packages/calendar/translations/hu.js index 2e4e5e13d..62696778a 100644 --- a/packages/calendar/translations/hu.js +++ b/packages/calendar/translations/hu.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Következő hónap', previousMonth: 'Előző hónap', + nextFullYear: 'Következő év', + previousFullYear: 'Előző év', }; diff --git a/packages/calendar/translations/it.js b/packages/calendar/translations/it.js index 8df8b8f84..24c30a9f9 100644 --- a/packages/calendar/translations/it.js +++ b/packages/calendar/translations/it.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Mese successivo', previousMonth: 'Mese precedente', + nextFullYear: 'Anno successivo', + previousFullYear: 'Anno precedente', }; diff --git a/packages/calendar/translations/nl.js b/packages/calendar/translations/nl.js index cf85a4abb..387d73b9f 100644 --- a/packages/calendar/translations/nl.js +++ b/packages/calendar/translations/nl.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Volgende maand', previousMonth: 'Vorige maand', + nextFullMonth: 'Volgend jaar', + previousFullMonth: 'Vorig jaar', }; diff --git a/packages/calendar/translations/pl.js b/packages/calendar/translations/pl.js index 6d022f308..e915dda2e 100644 --- a/packages/calendar/translations/pl.js +++ b/packages/calendar/translations/pl.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Następny miesiąc', previousMonth: 'Poprzedni miesiąc', + nextFullYear: 'Następny rok', + previousFullYear: 'Poprzedni rok', }; diff --git a/packages/calendar/translations/ro.js b/packages/calendar/translations/ro.js index da42be6ef..336f0e19f 100644 --- a/packages/calendar/translations/ro.js +++ b/packages/calendar/translations/ro.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Luna viitoare', previousMonth: 'Luna anterioară', + nextFullYear: 'An viitoare', + previousFullYear: 'An anterioară', }; diff --git a/packages/calendar/translations/ru.js b/packages/calendar/translations/ru.js index 90d3b8d4f..d9029ba7d 100644 --- a/packages/calendar/translations/ru.js +++ b/packages/calendar/translations/ru.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Следующий месяц', previousMonth: 'Предыдущий месяц', + nextFullMonth: 'Следующий год', + previousFullMonth: 'Предыдущий год', }; diff --git a/packages/calendar/translations/sk.js b/packages/calendar/translations/sk.js index a8f1fbf4a..cd3f86e69 100644 --- a/packages/calendar/translations/sk.js +++ b/packages/calendar/translations/sk.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Nasledujúci mesiac', previousMonth: 'Predchádzajúci mesiac', + nextFullYear: 'Nasledujúci rok', + previousFullYear: 'Predchádzajúci rok', }; diff --git a/packages/calendar/translations/uk.js b/packages/calendar/translations/uk.js index 4290a0130..add79d786 100644 --- a/packages/calendar/translations/uk.js +++ b/packages/calendar/translations/uk.js @@ -1,4 +1,6 @@ export default { nextMonth: 'Наступний місяць', previousMonth: 'Попередній місяць', + nextFullYear: 'Наступний року', + previousFullYear: 'Попередній року', }; diff --git a/packages/calendar/translations/zh.js b/packages/calendar/translations/zh.js index a6a688b4e..4a5068630 100644 --- a/packages/calendar/translations/zh.js +++ b/packages/calendar/translations/zh.js @@ -1,4 +1,6 @@ export default { nextMonth: '下一个月', previousMonth: '前一个月', + nextFullYear: '明一年', + previousFullYear: '前一年', };
+ @@ -68,7 +68,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 26 + 26 @@ -78,7 +78,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 27 + 27 @@ -88,7 +88,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 28 + 28 @@ -98,39 +98,39 @@ export default html` class="calendar__day-button" tabindex="-1" > - 29 + 29 + +
+ @@ -140,7 +140,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 3 + 3 @@ -150,7 +150,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 4 + 4 @@ -160,7 +160,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 5 + 5 @@ -170,7 +170,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 6 + 6 @@ -180,7 +180,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 7 + 7 @@ -190,7 +190,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 8 + 8
@@ -212,7 +212,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 10 + 10 @@ -222,7 +222,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 11 + 11 @@ -232,7 +232,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 12 + 12 @@ -242,7 +242,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 13 + 13 @@ -252,7 +252,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 14 + 14 @@ -262,7 +262,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 15 + 15
@@ -284,7 +284,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 17 + 17 @@ -294,7 +294,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 18 + 18 @@ -304,7 +304,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 19 + 19 @@ -314,7 +314,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 20 + 20 @@ -324,7 +324,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 21 + 21 @@ -334,7 +334,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 22 + 22
@@ -356,7 +356,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 24 + 24 @@ -366,7 +366,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 25 + 25 @@ -376,7 +376,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 26 + 26 @@ -386,7 +386,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 27 + 27 @@ -396,49 +396,49 @@ export default html` class="calendar__day-button" tabindex="-1" > - 28 + 28 +
+ + + @@ -448,7 +448,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 2 + 2 @@ -458,7 +458,7 @@ export default html` class="calendar__day-button" tabindex="-1" > - 3 + 3 @@ -468,17 +468,17 @@ export default html` class="calendar__day-button" tabindex="-1" > - 4 + 4 +