feat(calendar): add reusable calendar
Co-authored-by: Erik Kroes <erik.kroes@ing.com> Co-authored-by: Gerjan van Geest <gerjan.van.geest@ing.com> Co-authored-by: Thijs Louisse <thijs.louisse@ing.com> Co-authored-by: Thomas Allmer <thomas.allmer@ing.com>
This commit is contained in:
parent
043106c1cf
commit
9fc5488175
44 changed files with 3275 additions and 0 deletions
44
packages/calendar/README.md
Normal file
44
packages/calendar/README.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Calendar
|
||||||
|
|
||||||
|
[//]: # (AUTO INSERT HEADER PREPUBLISH)
|
||||||
|
|
||||||
|
`lion-calendar` is a reusable and accessible calendar view.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- fully accessible keyboard navigation (Arrow Keys, PgUp, PgDn, ALT+PgUp, ALT+PgDn)
|
||||||
|
- **minDate**: disables all dates before a given date
|
||||||
|
- **maxDate**: disables all dates after a given date
|
||||||
|
- **disableDates**: disables some dates within an available range
|
||||||
|
- **selectedDate**: currently selected date
|
||||||
|
- **centralDate**: date that determines the currently visible month and that will be focused when keyboard moves the focus to the month grid
|
||||||
|
- **focusedDate**: (getter only) currently focused date (if there is any with real focus)
|
||||||
|
- **focusDate(date)**: focus on a certain date
|
||||||
|
- **focusSelectedDate()**: focus on the current selected date
|
||||||
|
- **focusCentralDate()**: focus on the current central date
|
||||||
|
- **firstDayOfWeek**: typically Sunday (default) or Monday
|
||||||
|
- **weekdayHeaderNotation**: long/short/narrow for the current locale (e.g. Thursday/Thu/T)
|
||||||
|
- **locale**: different locale for the current component only
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i --save @lion/calendar
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import '@lion/calendar/lion-calendar.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```html
|
||||||
|
<lion-calendar
|
||||||
|
.minDate="${new Date()}"
|
||||||
|
.maxDate="${new Date('2019/12/09')}"
|
||||||
|
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
|
||||||
|
>
|
||||||
|
</lion-calendar>
|
||||||
|
```
|
||||||
1
packages/calendar/index.js
Normal file
1
packages/calendar/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { LionCalendar } from './src/LionCalendar.js';
|
||||||
3
packages/calendar/lion-calendar.js
Normal file
3
packages/calendar/lion-calendar.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { LionCalendar } from './src/LionCalendar.js';
|
||||||
|
|
||||||
|
customElements.define('lion-calendar', LionCalendar);
|
||||||
43
packages/calendar/package.json
Normal file
43
packages/calendar/package.json
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "@lion/calendar",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Reusable calendar component",
|
||||||
|
"author": "ing-bank",
|
||||||
|
"homepage": "https://github.com/ing-bank/lion/",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ing-bank/lion.git",
|
||||||
|
"directory": "packages/calendar"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../scripts/npm-prepublish.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"lion",
|
||||||
|
"web-components",
|
||||||
|
"calendar"
|
||||||
|
],
|
||||||
|
"main": "index.js",
|
||||||
|
"module": "index.js",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"stories",
|
||||||
|
"test",
|
||||||
|
"translations",
|
||||||
|
"*.js"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@lion/core": "^0.1.4",
|
||||||
|
"@lion/localize": "^0.1.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@lion/button": "^0.1.7",
|
||||||
|
"@open-wc/demoing-storybook": "^0.2.0",
|
||||||
|
"@open-wc/testing": "^0.11.1",
|
||||||
|
"sinon": "^7.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
547
packages/calendar/src/LionCalendar.js
Normal file
547
packages/calendar/src/LionCalendar.js
Normal file
|
|
@ -0,0 +1,547 @@
|
||||||
|
import { html, LitElement } from '@lion/core';
|
||||||
|
import { localize, getWeekdayNames, getMonthNames, LocalizeMixin } from '@lion/localize';
|
||||||
|
import { createMultipleMonth } from './utils/createMultipleMonth.js';
|
||||||
|
import { dayTemplate } from './utils/dayTemplate.js';
|
||||||
|
import { dataTemplate } from './utils/dataTemplate.js';
|
||||||
|
import { getFirstDayNextMonth } from './utils/getFirstDayNextMonth.js';
|
||||||
|
import { getLastDayPreviousMonth } from './utils/getLastDayPreviousMonth.js';
|
||||||
|
import { isSameDate } from './utils/isSameDate.js';
|
||||||
|
import { calendarStyle } from './calendarStyle.js';
|
||||||
|
import './utils/differentKeyNamesShimIE.js';
|
||||||
|
import { createDay } from './utils/createDay.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @customElement
|
||||||
|
*/
|
||||||
|
export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
|
static get localizeNamespaces() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'lion-calendar': locale => {
|
||||||
|
switch (locale) {
|
||||||
|
case 'bg-BG':
|
||||||
|
case 'bg':
|
||||||
|
return import('../translations/bg.js');
|
||||||
|
case 'cs-CZ':
|
||||||
|
case 'cs':
|
||||||
|
return import('../translations/cs.js');
|
||||||
|
case 'de-AT':
|
||||||
|
case 'de-DE':
|
||||||
|
case 'de':
|
||||||
|
return import('../translations/de.js');
|
||||||
|
case 'en-AU':
|
||||||
|
case 'en-GB':
|
||||||
|
case 'en-US':
|
||||||
|
case 'en':
|
||||||
|
return import('../translations/en.js');
|
||||||
|
case 'es-ES':
|
||||||
|
case 'es':
|
||||||
|
return import('../translations/es.js');
|
||||||
|
case 'fr-FR':
|
||||||
|
case 'fr-BE':
|
||||||
|
case 'fr':
|
||||||
|
return import('../translations/fr.js');
|
||||||
|
case 'hu-HU':
|
||||||
|
case 'hu':
|
||||||
|
return import('../translations/hu.js');
|
||||||
|
case 'it-IT':
|
||||||
|
case 'it':
|
||||||
|
return import('../translations/it.js');
|
||||||
|
case 'nl-BE':
|
||||||
|
case 'nl-NL':
|
||||||
|
case 'nl':
|
||||||
|
return import('../translations/nl.js');
|
||||||
|
case 'pl-PL':
|
||||||
|
case 'pl':
|
||||||
|
return import('../translations/pl.js');
|
||||||
|
case 'ro-RO':
|
||||||
|
case 'ro':
|
||||||
|
return import('../translations/ro.js');
|
||||||
|
case 'ru-RU':
|
||||||
|
case 'ru':
|
||||||
|
return import('../translations/ru.js');
|
||||||
|
case 'sk-SK':
|
||||||
|
case 'sk':
|
||||||
|
return import('../translations/sk.js');
|
||||||
|
case 'uk-UA':
|
||||||
|
case 'uk':
|
||||||
|
return import('../translations/uk.js');
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown locale: ${locale}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...super.localizeNamespaces,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Minimum date. All dates before will be disabled
|
||||||
|
*/
|
||||||
|
minDate: { type: Date },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum date. All dates after will be disabled
|
||||||
|
*/
|
||||||
|
maxDate: { type: Date },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable certain dates
|
||||||
|
*/
|
||||||
|
disableDates: { type: Function },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weekday header notation, based on Intl DatetimeFormat:
|
||||||
|
* - 'long' (e.g., Thursday)
|
||||||
|
* - 'short' (e.g., Thu)
|
||||||
|
* - 'narrow' (e.g., T).
|
||||||
|
* Default is 'short'
|
||||||
|
*/
|
||||||
|
weekdayHeaderNotation: { type: String },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Different locale for this component scope
|
||||||
|
*/
|
||||||
|
locale: { type: String },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently focused date (if any)
|
||||||
|
*/
|
||||||
|
__focusedDate: { type: Date },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data to render current month grid
|
||||||
|
*/
|
||||||
|
__data: { type: Object },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
// Defaults
|
||||||
|
this.__data = {};
|
||||||
|
this.minDate = null;
|
||||||
|
this.maxDate = null;
|
||||||
|
this.dayPreprocessor = day => day;
|
||||||
|
this.disableDates = () => false;
|
||||||
|
this.firstDayOfWeek = 0;
|
||||||
|
this.weekdayHeaderNotation = 'short';
|
||||||
|
this.__today = new Date();
|
||||||
|
this.centralDate = this.__today;
|
||||||
|
this.__focusedDate = null;
|
||||||
|
this.__connectedCallbackDone = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [calendarStyle];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="calendar" role="application">
|
||||||
|
${this.__renderHeader()} ${this.__renderData()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get focusedDate() {
|
||||||
|
return this.__focusedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
goToNextMonth() {
|
||||||
|
this.__modifyDate(1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
|
||||||
|
}
|
||||||
|
|
||||||
|
goToPreviousMonth() {
|
||||||
|
this.__modifyDate(-1, { dateType: 'centralDate', type: 'Month', mode: 'both' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async focusDate(date) {
|
||||||
|
this.centralDate = date;
|
||||||
|
await this.updateComplete;
|
||||||
|
this.focusCentralDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusCentralDate() {
|
||||||
|
const button = this.shadowRoot.querySelector('button[tabindex="0"]');
|
||||||
|
button.focus();
|
||||||
|
this.__focusedDate = this.centralDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
async focusSelectedDate() {
|
||||||
|
await this.focusDate(this.selectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
// eslint-disable-next-line wc/guard-super-call
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
this.__connectedCallbackDone = true;
|
||||||
|
|
||||||
|
this.__calculateInitialCentralDate();
|
||||||
|
|
||||||
|
// 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.__addEventDelegationForClickDate();
|
||||||
|
this.__addEventDelegationForFocusDate();
|
||||||
|
this.__addEventDelegationForBlurDate();
|
||||||
|
this.__addEventForKeyboardNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changed) {
|
||||||
|
if (changed.has('__focusedDate') && this.__focusedDate) {
|
||||||
|
this.focusCentralDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_requestUpdate(name, oldValue) {
|
||||||
|
super._requestUpdate(name, oldValue);
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
disableDates: () => this.__disableDatesChanged(),
|
||||||
|
centralDate: () => this.__centralDateChanged(),
|
||||||
|
__focusedDate: () => this.__focusedDateChanged(),
|
||||||
|
};
|
||||||
|
if (map[name]) {
|
||||||
|
map[name]();
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateDataOn = ['centralDate', 'minDate', 'maxDate', 'selectedDate', 'disableDates'];
|
||||||
|
|
||||||
|
if (updateDataOn.includes(name) && this.__connectedCallbackDone) {
|
||||||
|
this.__data = this.__createData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__calculateInitialCentralDate() {
|
||||||
|
if (this.centralDate === this.__today && this.selectedDate) {
|
||||||
|
// initialised with selectedDate only if user didn't provide another one
|
||||||
|
this.centralDate = this.selectedDate;
|
||||||
|
} else {
|
||||||
|
this.__ensureValidCentralDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__renderHeader() {
|
||||||
|
const month = getMonthNames({ locale: this.__getLocale() })[this.centralDate.getMonth()];
|
||||||
|
const year = this.centralDate.getFullYear();
|
||||||
|
return html`
|
||||||
|
<div class="calendar__header">
|
||||||
|
${this.__renderPreviousButton()}
|
||||||
|
<h2
|
||||||
|
class="calendar__month-heading"
|
||||||
|
id="month_and_year"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
|
>
|
||||||
|
${month} ${year}
|
||||||
|
</h2>
|
||||||
|
${this.__renderNextButton()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
__renderData() {
|
||||||
|
return dataTemplate(this.__data, {
|
||||||
|
monthsLabels: getMonthNames({ locale: this.__getLocale() }),
|
||||||
|
weekdaysShort: getWeekdayNames({
|
||||||
|
locale: this.__getLocale(),
|
||||||
|
style: this.weekdayHeaderNotation,
|
||||||
|
firstDayOfWeek: this.firstDayOfWeek,
|
||||||
|
}),
|
||||||
|
weekdays: getWeekdayNames({
|
||||||
|
locale: this.__getLocale(),
|
||||||
|
style: 'long',
|
||||||
|
firstDayOfWeek: this.firstDayOfWeek,
|
||||||
|
}),
|
||||||
|
dayTemplate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
__renderPreviousButton() {
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
class="calendar__previous-month-button"
|
||||||
|
aria-label=${this.msgLit('lion-calendar:previousMonth')}
|
||||||
|
title=${this.msgLit('lion-calendar:previousMonth')}
|
||||||
|
@click=${this.goToPreviousMonth}
|
||||||
|
?disabled=${this.isPreviousMonthDisabled}
|
||||||
|
>
|
||||||
|
<
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
__renderNextButton() {
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
class="calendar__next-month-button"
|
||||||
|
aria-label=${this.msgLit('lion-calendar:nextMonth')}
|
||||||
|
title=${this.msgLit('lion-calendar:nextMonth')}
|
||||||
|
@click=${this.goToNextMonth}
|
||||||
|
?disabled=${this.isNextMonthDisabled}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
__coreDayPreprocessor(_day, { currentMonth = false } = {}) {
|
||||||
|
const day = createDay(new Date(_day.date), _day);
|
||||||
|
const today = new Date();
|
||||||
|
day.central = isSameDate(day.date, this.centralDate);
|
||||||
|
day.previousMonth = currentMonth && day.date.getMonth() < currentMonth.getMonth();
|
||||||
|
day.currentMonth = currentMonth && day.date.getMonth() === currentMonth.getMonth();
|
||||||
|
day.nextMonth = currentMonth && day.date.getMonth() > currentMonth.getMonth();
|
||||||
|
day.selected = this.selectedDate ? isSameDate(day.date, this.selectedDate) : false;
|
||||||
|
day.past = day.date < today;
|
||||||
|
day.today = isSameDate(day.date, today);
|
||||||
|
day.future = day.date > today;
|
||||||
|
day.disabled = this.disableDates(day.date);
|
||||||
|
|
||||||
|
if (this.minDate && day.date < this.minDate) {
|
||||||
|
day.disabled = true;
|
||||||
|
}
|
||||||
|
if (this.maxDate && day.date > this.maxDate) {
|
||||||
|
day.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dayPreprocessor(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
__createData(options) {
|
||||||
|
const data = createMultipleMonth(this.centralDate, {
|
||||||
|
firstDayOfWeek: this.firstDayOfWeek,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
data.months.forEach((month, monthi) => {
|
||||||
|
month.weeks.forEach((week, weeki) => {
|
||||||
|
week.days.forEach((day, dayi) => {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const currentDay = data.months[monthi].weeks[weeki].days[dayi];
|
||||||
|
const currentMonth = data.months[monthi].weeks[0].days[6].date;
|
||||||
|
data.months[monthi].weeks[weeki].days[dayi] = this.__coreDayPreprocessor(currentDay, {
|
||||||
|
currentMonth,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isNextMonthDisabled =
|
||||||
|
this.maxDate && getFirstDayNextMonth(this.centralDate) > this.maxDate;
|
||||||
|
this.isPreviousMonthDisabled =
|
||||||
|
this.minDate && getLastDayPreviousMonth(this.centralDate) < this.minDate;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
__disableDatesChanged() {
|
||||||
|
this.__ensureValidCentralDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
__dateSelectedByUser(selectedDate) {
|
||||||
|
this.selectedDate = selectedDate;
|
||||||
|
this.__focusedDate = selectedDate;
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('user-selected-date-changed', {
|
||||||
|
detail: {
|
||||||
|
selectedDate,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__centralDateChanged() {
|
||||||
|
if (this.__connectedCallbackDone) {
|
||||||
|
this.__ensureValidCentralDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__focusedDateChanged() {
|
||||||
|
if (this.__focusedDate) {
|
||||||
|
this.centralDate = this.__focusedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__ensureValidCentralDate() {
|
||||||
|
if (!this.__isEnabledDate(this.centralDate)) {
|
||||||
|
this.centralDate = this.__findBestEnabledDateFor(this.centralDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__isEnabledDate(date) {
|
||||||
|
const processedDay = this.__coreDayPreprocessor({ date });
|
||||||
|
return !processedDay.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Date} date
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {String} [opts.mode] Find best date in `future/past/both`
|
||||||
|
*/
|
||||||
|
__findBestEnabledDateFor(date, { mode = 'both' } = {}) {
|
||||||
|
const futureDate =
|
||||||
|
this.minDate && this.minDate > date ? new Date(this.minDate) : new Date(date);
|
||||||
|
const pastDate = this.maxDate && this.maxDate < date ? new Date(this.maxDate) : new Date(date);
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
do {
|
||||||
|
i += 1;
|
||||||
|
if (mode === 'both' || mode === 'future') {
|
||||||
|
futureDate.setDate(futureDate.getDate() + 1);
|
||||||
|
if (this.__isEnabledDate(futureDate)) {
|
||||||
|
return futureDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mode === 'both' || mode === 'past') {
|
||||||
|
pastDate.setDate(pastDate.getDate() - 1);
|
||||||
|
if (this.__isEnabledDate(pastDate)) {
|
||||||
|
return pastDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (i < 750); // 2 years+
|
||||||
|
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
throw new Error(
|
||||||
|
`Could not find a selectable date within +/- 750 day for ${year}/${month}/${day}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__addEventDelegationForClickDate() {
|
||||||
|
const isDayCellOrButton = el =>
|
||||||
|
el.classList.contains('calendar__day-cell') || el.classList.contains('calendar__day-button');
|
||||||
|
this.__clickDateDelegation = this.__contentWrapperElement.addEventListener('click', ev => {
|
||||||
|
const el = ev.composedPath()[0];
|
||||||
|
if (isDayCellOrButton(el)) {
|
||||||
|
this.__dateSelectedByUser(el.date);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
__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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__removeEventDelegations() {
|
||||||
|
this.__contentWrapperElement.removeEventListener('click', this.__clickDateDelegation);
|
||||||
|
this.__contentWrapperElement.removeEventListener('focus', this.__focusDateDelegation);
|
||||||
|
this.__contentWrapperElement.removeEventListener('blur', this.__blurDateDelegation);
|
||||||
|
this.__contentWrapperElement.removeEventListener('keydown', this.__keyNavigationEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
__addEventForKeyboardNavigation() {
|
||||||
|
this.__keyNavigationEvent = this.__contentWrapperElement.addEventListener('keydown', ev => {
|
||||||
|
switch (ev.key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
this.__modifyDate(-7, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
this.__modifyDate(7, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Date', mode: 'past' });
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Date', mode: 'future' });
|
||||||
|
break;
|
||||||
|
case 'PageDown':
|
||||||
|
if (ev.altKey === true) {
|
||||||
|
this.__modifyDate(1, { dateType: '__focusedDate', type: 'FullYear', mode: 'future' });
|
||||||
|
} else {
|
||||||
|
this.__modifyDate(1, { dateType: '__focusedDate', type: 'Month', mode: 'future' });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'PageUp':
|
||||||
|
if (ev.altKey === true) {
|
||||||
|
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'FullYear', mode: 'past' });
|
||||||
|
} else {
|
||||||
|
this.__modifyDate(-1, { dateType: '__focusedDate', type: 'Month', mode: 'past' });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Tab':
|
||||||
|
this.__focusedDate = null;
|
||||||
|
break;
|
||||||
|
// no default
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
__modifyDate(modify, { dateType, type, mode } = {}) {
|
||||||
|
let tmpDate = new Date(this.centralDate);
|
||||||
|
tmpDate[`set${type}`](tmpDate[`get${type}`]() + modify);
|
||||||
|
|
||||||
|
if (!this.__isEnabledDate(tmpDate)) {
|
||||||
|
tmpDate = this.__findBestEnabledDateFor(tmpDate, { mode });
|
||||||
|
}
|
||||||
|
|
||||||
|
this[dateType] = tmpDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
__getLocale() {
|
||||||
|
return this.locale || localize.locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
packages/calendar/src/calendarStyle.js
Normal file
74
packages/calendar/src/calendarStyle.js
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { css } from '@lion/core';
|
||||||
|
|
||||||
|
export const calendarStyle = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid #adadad;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__month-heading {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__previous-month-button,
|
||||||
|
.calendar__next-month-button {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__grid {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__weekday-header {
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-button {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-button[today] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-button[selected] {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-button[previous-month],
|
||||||
|
.calendar__day-button[next-month] {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-button:hover {
|
||||||
|
border: 1px solid green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__day-button[disabled] {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #eee;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
29
packages/calendar/src/utils/createDay.js
Normal file
29
packages/calendar/src/utils/createDay.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export function createDay(
|
||||||
|
date = new Date(),
|
||||||
|
{
|
||||||
|
weekOrder,
|
||||||
|
central = false,
|
||||||
|
startOfWeek = false,
|
||||||
|
selected = false,
|
||||||
|
previousMonth = false,
|
||||||
|
currentMonth = false,
|
||||||
|
nextMonth = false,
|
||||||
|
past = false,
|
||||||
|
today = false,
|
||||||
|
future = false,
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
weekOrder,
|
||||||
|
central,
|
||||||
|
date,
|
||||||
|
startOfWeek,
|
||||||
|
selected,
|
||||||
|
previousMonth,
|
||||||
|
currentMonth,
|
||||||
|
nextMonth,
|
||||||
|
past,
|
||||||
|
today,
|
||||||
|
future,
|
||||||
|
};
|
||||||
|
}
|
||||||
25
packages/calendar/src/utils/createMonth.js
Normal file
25
packages/calendar/src/utils/createMonth.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { createWeek } from './createWeek.js';
|
||||||
|
|
||||||
|
export function createMonth(date, { firstDayOfWeek = 0 } = {}) {
|
||||||
|
if (Object.prototype.toString.call(date) !== '[object Date]') {
|
||||||
|
throw new Error('invalid date provided');
|
||||||
|
}
|
||||||
|
const firstDayOfMonth = new Date(date);
|
||||||
|
firstDayOfMonth.setDate(1);
|
||||||
|
const monthNumber = firstDayOfMonth.getMonth();
|
||||||
|
const weekOptions = { firstDayOfWeek };
|
||||||
|
|
||||||
|
const month = {
|
||||||
|
weeks: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
let nextWeek = createWeek(firstDayOfMonth, weekOptions);
|
||||||
|
do {
|
||||||
|
month.weeks.push(nextWeek);
|
||||||
|
const firstDayOfNextWeek = new Date(nextWeek.days[6].date); // last day of current week
|
||||||
|
firstDayOfNextWeek.setDate(firstDayOfNextWeek.getDate() + 1); // make it first day of next week
|
||||||
|
nextWeek = createWeek(firstDayOfNextWeek, weekOptions);
|
||||||
|
} while (nextWeek.days[0].date.getMonth() === monthNumber);
|
||||||
|
|
||||||
|
return month;
|
||||||
|
}
|
||||||
26
packages/calendar/src/utils/createMultipleMonth.js
Normal file
26
packages/calendar/src/utils/createMultipleMonth.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { createMonth } from './createMonth.js';
|
||||||
|
|
||||||
|
export function createMultipleMonth(
|
||||||
|
date,
|
||||||
|
{ firstDayOfWeek = 0, pastMonths = 0, futureMonths = 0 } = {},
|
||||||
|
) {
|
||||||
|
const multipleMonths = {
|
||||||
|
months: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = pastMonths; i > 0; i -= 1) {
|
||||||
|
const pastDate = new Date(date);
|
||||||
|
pastDate.setMonth(pastDate.getMonth() - i);
|
||||||
|
multipleMonths.months.push(createMonth(pastDate, { firstDayOfWeek }));
|
||||||
|
}
|
||||||
|
|
||||||
|
multipleMonths.months.push(createMonth(date, { firstDayOfWeek }));
|
||||||
|
|
||||||
|
for (let i = 0; i < futureMonths; i += 1) {
|
||||||
|
const futureDate = new Date(date);
|
||||||
|
futureDate.setMonth(futureDate.getMonth() + (i + 1));
|
||||||
|
multipleMonths.months.push(createMonth(futureDate, { firstDayOfWeek }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return multipleMonths;
|
||||||
|
}
|
||||||
30
packages/calendar/src/utils/createWeek.js
Normal file
30
packages/calendar/src/utils/createWeek.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { createDay } from './createDay.js';
|
||||||
|
|
||||||
|
export function createWeek(date, { firstDayOfWeek = 0 } = {}) {
|
||||||
|
if (Object.prototype.toString.call(date) !== '[object Date]') {
|
||||||
|
throw new Error('invalid date provided');
|
||||||
|
}
|
||||||
|
let weekStartDate = new Date(date);
|
||||||
|
|
||||||
|
const tmpDate = new Date(date);
|
||||||
|
while (tmpDate.getDay() !== firstDayOfWeek) {
|
||||||
|
tmpDate.setDate(tmpDate.getDate() - 1);
|
||||||
|
weekStartDate = new Date(tmpDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const week = {
|
||||||
|
days: [],
|
||||||
|
};
|
||||||
|
for (let i = 0; i < 7; i += 1) {
|
||||||
|
if (i !== 0) {
|
||||||
|
weekStartDate.setDate(weekStartDate.getDate() + 1);
|
||||||
|
}
|
||||||
|
week.days.push(
|
||||||
|
createDay(new Date(weekStartDate), {
|
||||||
|
weekOrder: i,
|
||||||
|
startOfWeek: i === 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return week;
|
||||||
|
}
|
||||||
51
packages/calendar/src/utils/dataTemplate.js
Normal file
51
packages/calendar/src/utils/dataTemplate.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { html } from '@lion/core';
|
||||||
|
import { dayTemplate as defaultDayTemplate } from './dayTemplate.js';
|
||||||
|
|
||||||
|
export function dataTemplate(
|
||||||
|
data,
|
||||||
|
{ weekdaysShort, weekdays, monthsLabels, dayTemplate = defaultDayTemplate } = {},
|
||||||
|
) {
|
||||||
|
return html`
|
||||||
|
<div id="js-content-wrapper">
|
||||||
|
${data.months.map(
|
||||||
|
month => html`
|
||||||
|
<table
|
||||||
|
role="grid"
|
||||||
|
data-wrap-cols
|
||||||
|
aria-readonly="true"
|
||||||
|
class="calendar__grid"
|
||||||
|
aria-labelledby="month_and_year"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr role="row">
|
||||||
|
${weekdaysShort.map(
|
||||||
|
(header, i) => html`
|
||||||
|
<th
|
||||||
|
role="columnheader"
|
||||||
|
class="calendar__weekday-header"
|
||||||
|
scope="col"
|
||||||
|
aria-label="${weekdays[i]}"
|
||||||
|
>
|
||||||
|
${header}
|
||||||
|
</th>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${month.weeks.map(
|
||||||
|
week => html`
|
||||||
|
<tr role="row">
|
||||||
|
${week.days.map(day =>
|
||||||
|
dayTemplate(day, { weekdaysShort, weekdays, monthsLabels }),
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
46
packages/calendar/src/utils/dayTemplate.js
Normal file
46
packages/calendar/src/utils/dayTemplate.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { html, ifDefined } from '@lion/core';
|
||||||
|
|
||||||
|
const defaultMonthLabels = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: remove as much logic as possible from this template and move to processor
|
||||||
|
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];
|
||||||
|
return html`
|
||||||
|
<td role="gridcell" class="calendar__day-cell">
|
||||||
|
<button
|
||||||
|
.date=${day.date}
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex=${day.central ? '0' : '-1'}
|
||||||
|
aria-label=${`${dayNumber} ${monthName} ${year} ${weekdayName}`}
|
||||||
|
aria-selected=${day.selected ? 'true' : 'false'}
|
||||||
|
aria-current=${ifDefined(day.today ? 'date' : undefined)}
|
||||||
|
?disabled=${day.disabled}
|
||||||
|
?selected=${day.selected}
|
||||||
|
?past=${day.past}
|
||||||
|
?today=${day.today}
|
||||||
|
?future=${day.future}
|
||||||
|
?previous-month=${day.previousMonth}
|
||||||
|
?current-month=${day.currentMonth}
|
||||||
|
?next-month=${day.nextMonth}
|
||||||
|
>
|
||||||
|
${day.date.getDate()}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
}
|
||||||
33
packages/calendar/src/utils/differentKeyNamesShimIE.js
Normal file
33
packages/calendar/src/utils/differentKeyNamesShimIE.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
const event = KeyboardEvent.prototype;
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(event, 'key');
|
||||||
|
if (descriptor) {
|
||||||
|
const keys = {
|
||||||
|
Win: 'Meta',
|
||||||
|
Scroll: 'ScrollLock',
|
||||||
|
Spacebar: ' ',
|
||||||
|
|
||||||
|
Down: 'ArrowDown',
|
||||||
|
Left: 'ArrowLeft',
|
||||||
|
Right: 'ArrowRight',
|
||||||
|
Up: 'ArrowUp',
|
||||||
|
|
||||||
|
Del: 'Delete',
|
||||||
|
Apps: 'ContextMenu',
|
||||||
|
Esc: 'Escape',
|
||||||
|
|
||||||
|
Multiply: '*',
|
||||||
|
Add: '+',
|
||||||
|
Subtract: '-',
|
||||||
|
Decimal: '.',
|
||||||
|
Divide: '/',
|
||||||
|
};
|
||||||
|
Object.defineProperty(event, 'key', {
|
||||||
|
// eslint-disable-next-line object-shorthand, func-names
|
||||||
|
get: function() {
|
||||||
|
const key = descriptor.get.call(this);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
return keys.hasOwnProperty(key) ? keys[key] : key;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
13
packages/calendar/src/utils/getFirstDayNextMonth.js
Normal file
13
packages/calendar/src/utils/getFirstDayNextMonth.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Gives the first day of the next month
|
||||||
|
*
|
||||||
|
* @param {Date} date
|
||||||
|
*
|
||||||
|
* returns {Date}
|
||||||
|
*/
|
||||||
|
export function getFirstDayNextMonth(date) {
|
||||||
|
const result = new Date(date);
|
||||||
|
result.setDate(1);
|
||||||
|
result.setMonth(date.getMonth() + 1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
12
packages/calendar/src/utils/getLastDayPreviousMonth.js
Normal file
12
packages/calendar/src/utils/getLastDayPreviousMonth.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Gives the last day of the previous month
|
||||||
|
*
|
||||||
|
* @param {Date} date
|
||||||
|
*
|
||||||
|
* returns {Date}
|
||||||
|
*/
|
||||||
|
export function getLastDayPreviousMonth(date) {
|
||||||
|
const previous = new Date(date);
|
||||||
|
previous.setDate(0);
|
||||||
|
return new Date(previous);
|
||||||
|
}
|
||||||
17
packages/calendar/src/utils/isSameDate.js
Normal file
17
packages/calendar/src/utils/isSameDate.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* Compares if two days are the same
|
||||||
|
*
|
||||||
|
* @param {Date} day1
|
||||||
|
* @param {Date} day2
|
||||||
|
*
|
||||||
|
* returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isSameDate(day1, day2) {
|
||||||
|
return (
|
||||||
|
day1 instanceof Date &&
|
||||||
|
day2 instanceof Date &&
|
||||||
|
day1.getDate() === day2.getDate() &&
|
||||||
|
day1.getMonth() === day2.getMonth() &&
|
||||||
|
day1.getFullYear() === day2.getFullYear()
|
||||||
|
);
|
||||||
|
}
|
||||||
136
packages/calendar/stories/index.stories.js
Executable file
136
packages/calendar/stories/index.stories.js
Executable file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { storiesOf, html } from '@open-wc/demoing-storybook';
|
||||||
|
import { css } from '@lion/core';
|
||||||
|
import '@lion/button/lion-button.js';
|
||||||
|
|
||||||
|
import '../lion-calendar.js';
|
||||||
|
|
||||||
|
const calendarDemoStyle = css`
|
||||||
|
.demo-calendar {
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
box-shadow: 0 0 16px #ccc;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
storiesOf('Calendar|Standalone', module)
|
||||||
|
.add(
|
||||||
|
'default',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar class="demo-calendar"></lion-calendar>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add('selectedDate', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const selectedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar class="demo-calendar" .selectedDate="${selectedDate}"></lion-calendar>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.add('centralDate', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const centralDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar class="demo-calendar" .centralDate="${centralDate}"></lion-calendar>
|
||||||
|
|
||||||
|
<p>Use TAB to see which date will be focused first.</p>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.add('control focus', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const selectedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
||||||
|
const centralDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar
|
||||||
|
id="js-demo-calendar"
|
||||||
|
class="demo-calendar"
|
||||||
|
.selectedDate="${selectedDate}"
|
||||||
|
.centralDate="${centralDate}"
|
||||||
|
></lion-calendar>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Focus:
|
||||||
|
<lion-button
|
||||||
|
@click="${() => document.querySelector('#js-demo-calendar').focusCentralDate()}"
|
||||||
|
>
|
||||||
|
Central date
|
||||||
|
</lion-button>
|
||||||
|
<lion-button
|
||||||
|
@click="${() => document.querySelector('#js-demo-calendar').focusSelectedDate()}"
|
||||||
|
>
|
||||||
|
Selected date
|
||||||
|
</lion-button>
|
||||||
|
<lion-button @click="${() => document.querySelector('#js-demo-calendar').focusDate(today)}">
|
||||||
|
Today
|
||||||
|
</lion-button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Be aware that the central date changes when a new date is focused.</p>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.add('minDate', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const minDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 2);
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar class="demo-calendar" .minDate="${minDate}"></lion-calendar>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.add('maxDate', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2);
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar class="demo-calendar" .maxDate="${maxDate}"></lion-calendar>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.add(
|
||||||
|
'disableDates',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar
|
||||||
|
class="demo-calendar"
|
||||||
|
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
|
||||||
|
></lion-calendar>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add('combined disabled dates', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, today.getDate());
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${calendarDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-calendar
|
||||||
|
class="demo-calendar"
|
||||||
|
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
|
||||||
|
.minDate="${new Date()}"
|
||||||
|
.maxDate="${maxDate}"
|
||||||
|
></lion-calendar>
|
||||||
|
`;
|
||||||
|
});
|
||||||
49
packages/calendar/test/keyboardEventShimIE.js
Normal file
49
packages/calendar/test/keyboardEventShimIE.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
if (typeof window.KeyboardEvent !== 'function') {
|
||||||
|
// e.g. is IE and needs "polyfill"
|
||||||
|
const KeyboardEvent = (event, _params) => {
|
||||||
|
// current spec for it https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
|
||||||
|
const params = {
|
||||||
|
bubbles: false,
|
||||||
|
cancelable: false,
|
||||||
|
view: document.defaultView,
|
||||||
|
key: false,
|
||||||
|
location: false,
|
||||||
|
ctrlKey: false,
|
||||||
|
shiftKey: false,
|
||||||
|
altKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
repeat: false,
|
||||||
|
..._params,
|
||||||
|
};
|
||||||
|
const modifiersListArray = [];
|
||||||
|
if (params.ctrlKey) {
|
||||||
|
modifiersListArray.push('Control');
|
||||||
|
}
|
||||||
|
if (params.shiftKey) {
|
||||||
|
modifiersListArray.push('Shift');
|
||||||
|
}
|
||||||
|
if (params.altKey) {
|
||||||
|
modifiersListArray.push('Alt');
|
||||||
|
}
|
||||||
|
if (params.metaKey) {
|
||||||
|
modifiersListArray.push('Meta');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ev = document.createEvent('KeyboardEvent');
|
||||||
|
// IE Spec for it https://technet.microsoft.com/en-us/windows/ff975297(v=vs.60)
|
||||||
|
ev.initKeyboardEvent(
|
||||||
|
event,
|
||||||
|
params.bubbles,
|
||||||
|
params.cancelable,
|
||||||
|
params.view,
|
||||||
|
params.key,
|
||||||
|
params.location,
|
||||||
|
modifiersListArray.join(' '),
|
||||||
|
params.repeat ? 1 : 0,
|
||||||
|
params.locale,
|
||||||
|
);
|
||||||
|
return ev;
|
||||||
|
};
|
||||||
|
KeyboardEvent.prototype = window.Event.prototype;
|
||||||
|
window.KeyboardEvent = KeyboardEvent;
|
||||||
|
}
|
||||||
1074
packages/calendar/test/lion-calendar.test.js
Normal file
1074
packages/calendar/test/lion-calendar.test.js
Normal file
File diff suppressed because it is too large
Load diff
221
packages/calendar/test/test-utils.js
Normal file
221
packages/calendar/test/test-utils.js
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
export const weekdayNames = {
|
||||||
|
'en-GB': {
|
||||||
|
Sunday: {
|
||||||
|
long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||||
|
short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction around calendar day DOM structure,
|
||||||
|
* allows for writing readable, 'DOM structure agnostic' tests
|
||||||
|
*/
|
||||||
|
export class DayObject {
|
||||||
|
constructor(dayEl) {
|
||||||
|
this.el = dayEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node references
|
||||||
|
*/
|
||||||
|
|
||||||
|
get calendarShadowRoot() {
|
||||||
|
return this.el.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
get cellEl() {
|
||||||
|
return this.el.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
get buttonEl() {
|
||||||
|
return this.el;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* States
|
||||||
|
*/
|
||||||
|
|
||||||
|
get isDisabled() {
|
||||||
|
return this.buttonEl.hasAttribute('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSelected() {
|
||||||
|
return this.buttonEl.hasAttribute('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isToday() {
|
||||||
|
return this.buttonEl.hasAttribute('today');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCentral() {
|
||||||
|
return this.buttonEl.getAttribute('tabindex') === '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFocused() {
|
||||||
|
this.calendarShadowRoot.activeElement;
|
||||||
|
this.buttonEl;
|
||||||
|
return this.calendarShadowRoot.activeElement === this.buttonEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
get monthday() {
|
||||||
|
return Number(this.buttonEl.textContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text
|
||||||
|
*/
|
||||||
|
|
||||||
|
get weekdayNameShort() {
|
||||||
|
const weekdayEls = Array.from(
|
||||||
|
this.el.parentElement.parentElement.querySelectorAll('.calendar__day-cell'),
|
||||||
|
);
|
||||||
|
const dayIndex = weekdayEls.indexOf(this.el.parentElement);
|
||||||
|
return weekdayNames['en-GB'].Sunday.short[dayIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
get weekdayNameLong() {
|
||||||
|
const weekdayEls = Array.from(
|
||||||
|
this.el.parentElement.parentElement.querySelectorAll('.calendar__day-cell'),
|
||||||
|
);
|
||||||
|
const dayIndex = weekdayEls.indexOf(this.el.parentElement);
|
||||||
|
return weekdayNames['en-GB'].Sunday.long[dayIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other
|
||||||
|
*/
|
||||||
|
get cellIndex() {
|
||||||
|
return Array.from(this.cellEl.parentElement.children).indexOf(this.cellEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction around calendar DOM structure,
|
||||||
|
* allows for writing readable, 'DOM structure agnostic' tests
|
||||||
|
*/
|
||||||
|
export class CalendarObject {
|
||||||
|
constructor(calendarEl) {
|
||||||
|
this.el = calendarEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node references
|
||||||
|
*/
|
||||||
|
|
||||||
|
get rootEl() {
|
||||||
|
return this.el.shadowRoot.querySelector('.calendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerEl() {
|
||||||
|
return this.el.shadowRoot.querySelector('.calendar__header');
|
||||||
|
}
|
||||||
|
|
||||||
|
get monthHeadingEl() {
|
||||||
|
return this.el.shadowRoot.querySelector('.calendar__month-heading');
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextMonthButtonEl() {
|
||||||
|
return this.el.shadowRoot.querySelector('.calendar__next-month-button');
|
||||||
|
}
|
||||||
|
|
||||||
|
get previousMonthButtonEl() {
|
||||||
|
return this.el.shadowRoot.querySelector('.calendar__previous-month-button');
|
||||||
|
}
|
||||||
|
|
||||||
|
get gridEl() {
|
||||||
|
return this.el.shadowRoot.querySelector('.calendar__grid');
|
||||||
|
}
|
||||||
|
|
||||||
|
get weekdayHeaderEls() {
|
||||||
|
return [].slice.call(this.el.shadowRoot.querySelectorAll('.calendar__weekday-header'));
|
||||||
|
}
|
||||||
|
|
||||||
|
get dayEls() {
|
||||||
|
return [].slice.call(
|
||||||
|
this.el.shadowRoot.querySelectorAll('.calendar__day-button[current-month]'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get previousMonthDayEls() {
|
||||||
|
return [].slice.call(
|
||||||
|
this.el.shadowRoot.querySelectorAll('.calendar__day-button[previous-month]'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextMonthDayEls() {
|
||||||
|
return [].slice.call(this.el.shadowRoot.querySelectorAll('.calendar__day-button[next-month]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
get dayObjs() {
|
||||||
|
return this.dayEls.map(d => new DayObject(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
get previousMonthDayObjs() {
|
||||||
|
return this.previousMonthDayEls.map(d => new DayObject(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextMonthDayObjs() {
|
||||||
|
return this.nextMonthDayEls.map(d => new DayObject(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getDayObj(monthDayNumber) {
|
||||||
|
return new DayObject(this.getDayEl(monthDayNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedDayObj() {
|
||||||
|
return this.dayObjs.find(d => d.selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
get centralDayObj() {
|
||||||
|
return this.dayObjs.find(d => d.isCentral);
|
||||||
|
}
|
||||||
|
|
||||||
|
get focusedDayObj() {
|
||||||
|
return this.dayObjs.find(d => d.el === this.el.shadowRoot.activeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Applies condition to all days, or days in filter
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* - Example 1: "[15, 20]"
|
||||||
|
* - Example 2: "(dayNumber) => dayNumber === 15" (1 based ,not zero based)
|
||||||
|
*/
|
||||||
|
checkForAllDayObjs(condition, filter) {
|
||||||
|
return this.dayEls.every(d => {
|
||||||
|
const dayObj = new DayObject(d);
|
||||||
|
const dayNumber = dayObj.monthday;
|
||||||
|
let shouldApply = true;
|
||||||
|
if (filter !== undefined) {
|
||||||
|
shouldApply = filter instanceof Array ? filter.includes(dayNumber) : filter(dayNumber);
|
||||||
|
}
|
||||||
|
// for instance, should be 'disabled' for the 15th and 20th day
|
||||||
|
return !shouldApply || (condition(dayObj) && shouldApply);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* States
|
||||||
|
*/
|
||||||
|
get activeMonthAndYear() {
|
||||||
|
return this.monthHeadingEl.textContent.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeMonth() {
|
||||||
|
return this.activeMonthAndYear.split(' ')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeYear() {
|
||||||
|
return this.activeMonthAndYear.split(' ')[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
45
packages/calendar/test/utils/createMonth.test.js
Normal file
45
packages/calendar/test/utils/createMonth.test.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { createMonth } from '../../src/utils/createMonth.js';
|
||||||
|
import { createWeek } from '../../src/utils/createWeek.js';
|
||||||
|
|
||||||
|
function compareMonth(obj) {
|
||||||
|
obj.weeks.forEach((week, weeki) => {
|
||||||
|
week.days.forEach((day, dayi) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
obj.weeks[weeki].days[dayi].date = obj.weeks[weeki].days[dayi].date.toISOString();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('createMonth', () => {
|
||||||
|
it('creates month data with Sunday as first day of week by default', () => {
|
||||||
|
expect(compareMonth(createMonth(new Date('2018/12/01')))).to.deep.equal(
|
||||||
|
compareMonth({
|
||||||
|
weeks: [
|
||||||
|
createWeek(new Date('2018/11/25'), { firstDayOfWeek: 0 }),
|
||||||
|
createWeek(new Date('2018/12/02'), { firstDayOfWeek: 0 }),
|
||||||
|
createWeek(new Date('2018/12/09'), { firstDayOfWeek: 0 }),
|
||||||
|
createWeek(new Date('2018/12/16'), { firstDayOfWeek: 0 }),
|
||||||
|
createWeek(new Date('2018/12/23'), { firstDayOfWeek: 0 }),
|
||||||
|
createWeek(new Date('2018/12/30'), { firstDayOfWeek: 0 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create month data for different first day of week', () => {
|
||||||
|
expect(compareMonth(createMonth(new Date('2018/12/01'), { firstDayOfWeek: 1 }))).to.deep.equal(
|
||||||
|
compareMonth({
|
||||||
|
weeks: [
|
||||||
|
createWeek(new Date('2018/11/26'), { firstDayOfWeek: 1 }),
|
||||||
|
createWeek(new Date('2018/12/03'), { firstDayOfWeek: 1 }),
|
||||||
|
createWeek(new Date('2018/12/10'), { firstDayOfWeek: 1 }),
|
||||||
|
createWeek(new Date('2018/12/17'), { firstDayOfWeek: 1 }),
|
||||||
|
createWeek(new Date('2018/12/24'), { firstDayOfWeek: 1 }),
|
||||||
|
createWeek(new Date('2018/12/31'), { firstDayOfWeek: 1 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
71
packages/calendar/test/utils/createMultipleMonth.test.js
Normal file
71
packages/calendar/test/utils/createMultipleMonth.test.js
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { createMultipleMonth } from '../../src/utils/createMultipleMonth.js';
|
||||||
|
import { createMonth } from '../../src/utils/createMonth.js';
|
||||||
|
|
||||||
|
function compareMultipleMonth(obj) {
|
||||||
|
obj.months.forEach((month, monthi) => {
|
||||||
|
month.weeks.forEach((week, weeki) => {
|
||||||
|
week.days.forEach((day, dayi) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
obj.months[monthi].weeks[weeki].days[dayi].date = obj.months[monthi].weeks[weeki].days[
|
||||||
|
dayi
|
||||||
|
].date.toISOString();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('createMultipleMonth', () => {
|
||||||
|
it('creates 1 month by default', () => {
|
||||||
|
expect(compareMultipleMonth(createMultipleMonth(new Date('2018/12/01')))).to.deep.equal(
|
||||||
|
compareMultipleMonth({
|
||||||
|
months: [createMonth(new Date('2018/12/01'))],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create extra months in the past', () => {
|
||||||
|
expect(
|
||||||
|
compareMultipleMonth(createMultipleMonth(new Date('2018/12/01'), { pastMonths: 2 })),
|
||||||
|
).to.deep.equal(
|
||||||
|
compareMultipleMonth({
|
||||||
|
months: [
|
||||||
|
createMonth(new Date('2018/10/01')),
|
||||||
|
createMonth(new Date('2018/11/01')),
|
||||||
|
createMonth(new Date('2018/12/01')),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create extra months in the future', () => {
|
||||||
|
expect(
|
||||||
|
compareMultipleMonth(createMultipleMonth(new Date('2018/12/01'), { futureMonths: 2 })),
|
||||||
|
).to.deep.equal(
|
||||||
|
compareMultipleMonth({
|
||||||
|
months: [
|
||||||
|
createMonth(new Date('2018/12/01')),
|
||||||
|
createMonth(new Date('2019/01/01')),
|
||||||
|
createMonth(new Date('2019/02/01')),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create extra months in the past and future', () => {
|
||||||
|
expect(
|
||||||
|
compareMultipleMonth(
|
||||||
|
createMultipleMonth(new Date('2018/12/01'), { pastMonths: 1, futureMonths: 1 }),
|
||||||
|
),
|
||||||
|
).to.deep.equal(
|
||||||
|
compareMultipleMonth({
|
||||||
|
months: [
|
||||||
|
createMonth(new Date('2018/11/01')),
|
||||||
|
createMonth(new Date('2018/12/01')),
|
||||||
|
createMonth(new Date('2019/01/01')),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
47
packages/calendar/test/utils/createWeek.test.js
Normal file
47
packages/calendar/test/utils/createWeek.test.js
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { createWeek } from '../../src/utils/createWeek.js';
|
||||||
|
import { createDay } from '../../src/utils/createDay.js';
|
||||||
|
|
||||||
|
function compareWeek(obj) {
|
||||||
|
for (let i = 0; i < 7; i += 1) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
obj.days[i].date = obj.days[i].date.toISOString();
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('createWeek', () => {
|
||||||
|
it('creates week data starting from Sunday by default', () => {
|
||||||
|
// https://www.timeanddate.com/date/weeknumber.html?d1=30&m1=12&y1=2018&w2=&y2=&wncm=1&wncd=1&wncs=4&fdow=7
|
||||||
|
expect(compareWeek(createWeek(new Date('2018/12/30')))).to.deep.equal(
|
||||||
|
compareWeek({
|
||||||
|
days: [
|
||||||
|
createDay(new Date('2018/12/30'), { weekOrder: 0, startOfWeek: true }),
|
||||||
|
createDay(new Date('2018/12/31'), { weekOrder: 1 }),
|
||||||
|
createDay(new Date('2019/01/01'), { weekOrder: 2 }),
|
||||||
|
createDay(new Date('2019/01/02'), { weekOrder: 3 }),
|
||||||
|
createDay(new Date('2019/01/03'), { weekOrder: 4 }),
|
||||||
|
createDay(new Date('2019/01/04'), { weekOrder: 5 }),
|
||||||
|
createDay(new Date('2019/01/05'), { weekOrder: 6 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create week data starting from different day', () => {
|
||||||
|
// https://www.timeanddate.com/date/weeknumber.html?d1=31&m1=12&y1=2018&w2=&y2=&wncm=1&wncd=1&wncs=4&fdow=0
|
||||||
|
expect(compareWeek(createWeek(new Date('2018/12/31'), { firstDayOfWeek: 1 }))).to.deep.equal(
|
||||||
|
compareWeek({
|
||||||
|
days: [
|
||||||
|
createDay(new Date('2018/12/31'), { weekOrder: 0, startOfWeek: true }),
|
||||||
|
createDay(new Date('2019/01/01'), { weekOrder: 1 }),
|
||||||
|
createDay(new Date('2019/01/02'), { weekOrder: 2 }),
|
||||||
|
createDay(new Date('2019/01/03'), { weekOrder: 3 }),
|
||||||
|
createDay(new Date('2019/01/04'), { weekOrder: 4 }),
|
||||||
|
createDay(new Date('2019/01/05'), { weekOrder: 5 }),
|
||||||
|
createDay(new Date('2019/01/06'), { weekOrder: 6 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
24
packages/calendar/test/utils/dataTemplate.test.js
Normal file
24
packages/calendar/test/utils/dataTemplate.test.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* eslint-disable no-unused-expressions */
|
||||||
|
import { expect, fixture } from '@open-wc/testing';
|
||||||
|
|
||||||
|
import { createMultipleMonth } from '../../src/utils/createMultipleMonth.js';
|
||||||
|
import { dataTemplate } from '../../src/utils/dataTemplate.js';
|
||||||
|
import { weekdayNames } from '../test-utils.js';
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import snapshot_enGB_Sunday_201812 from './snapshots/monthTemplate_en-GB_Sunday_2018-12.js';
|
||||||
|
|
||||||
|
describe('dataTemplate', () => {
|
||||||
|
it('renders one month table', async () => {
|
||||||
|
const date = new Date('2018/12/01');
|
||||||
|
const month = createMultipleMonth(date, { firstDayOfWeek: 0 });
|
||||||
|
const el = await fixture(
|
||||||
|
dataTemplate(month, {
|
||||||
|
weekdaysShort: weekdayNames['en-GB'].Sunday.short,
|
||||||
|
weekdays: weekdayNames['en-GB'].Sunday.long,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(el).dom.to.equal(snapshot_enGB_Sunday_201812);
|
||||||
|
});
|
||||||
|
});
|
||||||
28
packages/calendar/test/utils/dayTemplate.test.js
Normal file
28
packages/calendar/test/utils/dayTemplate.test.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* 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';
|
||||||
|
|
||||||
|
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'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(el).dom.to.equal(`
|
||||||
|
<td role="gridcell" class="calendar__day-cell">
|
||||||
|
<button
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-label="19 April 2019 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
>
|
||||||
|
19
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
11
packages/calendar/test/utils/getFirstDayNextMonth.test.js
Normal file
11
packages/calendar/test/utils/getFirstDayNextMonth.test.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { formatDate } from '../../../localize/src/date/formatDate.js';
|
||||||
|
import { getFirstDayNextMonth } from '../../src/utils/getFirstDayNextMonth.js';
|
||||||
|
|
||||||
|
describe('getFirstDayNextMonth', () => {
|
||||||
|
it('returns the first day of the next month', () => {
|
||||||
|
expect(formatDate(getFirstDayNextMonth(new Date('2001/01/01')))).to.be.equal('01/02/2001');
|
||||||
|
expect(formatDate(getFirstDayNextMonth(new Date('2001/10/10')))).to.be.equal('01/11/2001');
|
||||||
|
expect(formatDate(getFirstDayNextMonth(new Date('2000/03/10')))).to.be.equal('01/04/2000');
|
||||||
|
});
|
||||||
|
});
|
||||||
11
packages/calendar/test/utils/getLastDayPreviousMonth.test.js
Normal file
11
packages/calendar/test/utils/getLastDayPreviousMonth.test.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { formatDate } from '../../../localize/src/date/formatDate.js';
|
||||||
|
import { getLastDayPreviousMonth } from '../../src/utils/getLastDayPreviousMonth.js';
|
||||||
|
|
||||||
|
describe('getLastDayPreviousMonth', () => {
|
||||||
|
it('returns the last day of the previous month', () => {
|
||||||
|
expect(formatDate(getLastDayPreviousMonth(new Date('2001/01/01')))).to.be.equal('31/12/2000');
|
||||||
|
expect(formatDate(getLastDayPreviousMonth(new Date('2001/10/10')))).to.be.equal('30/09/2001');
|
||||||
|
expect(formatDate(getLastDayPreviousMonth(new Date('2000/03/10')))).to.be.equal('29/02/2000');
|
||||||
|
});
|
||||||
|
});
|
||||||
19
packages/calendar/test/utils/isSameDate.test.js
Normal file
19
packages/calendar/test/utils/isSameDate.test.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { isSameDate } from '../../src/utils/isSameDate.js';
|
||||||
|
|
||||||
|
describe('isSameDate', () => {
|
||||||
|
it('returns true if the same date is given', () => {
|
||||||
|
const day1 = new Date('2001/01/01');
|
||||||
|
const day2 = new Date('2001/01/01');
|
||||||
|
const day3 = new Date('2002/02/02');
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,488 @@
|
||||||
|
const html = strings => strings[0];
|
||||||
|
|
||||||
|
export default html`
|
||||||
|
<div id="js-content-wrapper">
|
||||||
|
<table
|
||||||
|
aria-labelledby="month_and_year"
|
||||||
|
aria-readonly="true"
|
||||||
|
class="calendar__grid"
|
||||||
|
data-wrap-cols=""
|
||||||
|
role="grid"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr role="row">
|
||||||
|
<th class="calendar__weekday-header" aria-label="Sunday" scope="col" role="columnheader">
|
||||||
|
Sun
|
||||||
|
</th>
|
||||||
|
<th class="calendar__weekday-header" aria-label="Monday" scope="col" role="columnheader">
|
||||||
|
Mon
|
||||||
|
</th>
|
||||||
|
<th class="calendar__weekday-header" aria-label="Tuesday" scope="col" role="columnheader">
|
||||||
|
Tue
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="calendar__weekday-header"
|
||||||
|
aria-label="Wednesday"
|
||||||
|
scope="col"
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
|
Wed
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="calendar__weekday-header"
|
||||||
|
aria-label="Thursday"
|
||||||
|
scope="col"
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
|
Thu
|
||||||
|
</th>
|
||||||
|
<th class="calendar__weekday-header" aria-label="Friday" scope="col" role="columnheader">
|
||||||
|
Fri
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="calendar__weekday-header"
|
||||||
|
aria-label="Saturday"
|
||||||
|
scope="col"
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
|
Sat
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr role="row">
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="25 November 2018 Sunday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="26 November 2018 Monday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
26
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="27 November 2018 Tuesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
27
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="28 November 2018 Wednesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
28
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="29 November 2018 Thursday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
29
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="30 November 2018 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="1 December 2018 Saturday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr role="row">
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="2 December 2018 Sunday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
2
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="3 December 2018 Monday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
3
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="4 December 2018 Tuesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
4
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="5 December 2018 Wednesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="6 December 2018 Thursday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
6
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="7 December 2018 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
7
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="8 December 2018 Saturday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
8
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr role="row">
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="9 December 2018 Sunday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
9
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="10 December 2018 Monday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="11 December 2018 Tuesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
11
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="12 December 2018 Wednesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
12
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="13 December 2018 Thursday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
13
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="14 December 2018 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
14
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="15 December 2018 Saturday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
15
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr role="row">
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="16 December 2018 Sunday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
16
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="17 December 2018 Monday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
17
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="18 December 2018 Tuesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
18
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="19 December 2018 Wednesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
19
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="20 December 2018 Thursday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
20
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="21 December 2018 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
21
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="22 December 2018 Saturday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
22
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr role="row">
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="23 December 2018 Sunday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
23
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="24 December 2018 Monday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
24
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="25 December 2018 Tuesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="26 December 2018 Wednesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
26
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="27 December 2018 Thursday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
27
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="28 December 2018 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
28
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="29 December 2018 Saturday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
29
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr role="row">
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="30 December 2018 Sunday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="31 December 2018 Monday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
31
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="1 January 2019 Tuesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="2 January 2019 Wednesday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
2
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="3 January 2019 Thursday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
3
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="4 January 2019 Friday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
4
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="calendar__day-cell" role="gridcell">
|
||||||
|
<button
|
||||||
|
aria-label="5 January 2019 Saturday"
|
||||||
|
aria-selected="false"
|
||||||
|
class="calendar__day-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
4
packages/calendar/translations/bg.js
Normal file
4
packages/calendar/translations/bg.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Следващ месец',
|
||||||
|
previousMonth: 'Предишен месец',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/cs.js
Normal file
4
packages/calendar/translations/cs.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Příští měsíc',
|
||||||
|
previousMonth: 'Předchozí měsíc',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/de.js
Normal file
4
packages/calendar/translations/de.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Nächster Monat',
|
||||||
|
previousMonth: 'Vorheriger Monat',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/en.js
Normal file
4
packages/calendar/translations/en.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Next month',
|
||||||
|
previousMonth: 'Previous month',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/es.js
Normal file
4
packages/calendar/translations/es.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Mes siguiente',
|
||||||
|
previousMonth: 'Mes anterior',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/fr.js
Normal file
4
packages/calendar/translations/fr.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Mois prochain',
|
||||||
|
previousMonth: 'Mois précédent',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/hu.js
Normal file
4
packages/calendar/translations/hu.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Következő hónap',
|
||||||
|
previousMonth: 'Előző hónap',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/it.js
Normal file
4
packages/calendar/translations/it.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Mese successivo',
|
||||||
|
previousMonth: 'Mese precedente',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/nl.js
Normal file
4
packages/calendar/translations/nl.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Volgende maand',
|
||||||
|
previousMonth: 'Vorige maand',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/pl.js
Normal file
4
packages/calendar/translations/pl.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Następny miesiąc',
|
||||||
|
previousMonth: 'Poprzedni miesiąc',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/ro.js
Normal file
4
packages/calendar/translations/ro.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Luna viitoare',
|
||||||
|
previousMonth: 'Luna anterioară',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/ru.js
Normal file
4
packages/calendar/translations/ru.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Следующий месяц',
|
||||||
|
previousMonth: 'Предыдущий месяц',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/sk.js
Normal file
4
packages/calendar/translations/sk.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Nasledujúci mesiac',
|
||||||
|
previousMonth: 'Predchádzajúci mesiac',
|
||||||
|
};
|
||||||
4
packages/calendar/translations/uk.js
Normal file
4
packages/calendar/translations/uk.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
nextMonth: 'Наступний місяць',
|
||||||
|
previousMonth: 'Попередній місяць',
|
||||||
|
};
|
||||||
|
|
@ -22,3 +22,4 @@ import '../packages/localize/stories/index.stories.js';
|
||||||
import '../packages/overlays/stories/index.stories.js';
|
import '../packages/overlays/stories/index.stories.js';
|
||||||
import '../packages/popup/stories/index.stories.js';
|
import '../packages/popup/stories/index.stories.js';
|
||||||
import '../packages/tooltip/stories/index.stories.js';
|
import '../packages/tooltip/stories/index.stories.js';
|
||||||
|
import '../packages/calendar/stories/index.stories.js';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue