@unicef-polymer/etools-unicef
Version:
eTools UNICEF library of reusable components
896 lines (848 loc) • 29.2 kB
JavaScript
import { __decorate } from "tslib";
import { LitElement, html } from 'lit';
import { property, customElement } from 'lit/decorators.js';
import '../etools-icon-button/etools-icon-button';
import { elevationStyles } from './styles/elevation-styles';
import { translatedDayNames, translatedDaysFirstLetter, translatedMonthNames } from './assets/translations';
import dayjs from 'dayjs';
const dateLib = dayjs || window.moment;
const controlFormat = 'YYYY-MM-DD';
if (!dateLib) {
throw new Error('CalendarLite: dayjs or moment is not loaded');
}
let CalendarLite = class CalendarLite extends LitElement {
set date(date) {
if (!date) {
this._date = undefined;
this._populate(this.date);
return;
}
if (typeof date === 'string') {
date = dateLib(date, controlFormat).toDate();
}
if (this._date && date && this._date.getTime() === date.getTime()) {
return;
}
this._date = date;
this._populate(this.date);
this.dispatchEvent(new CustomEvent('date-changed', {
detail: { value: this.date },
bubbles: true,
composed: true
}));
}
get date() {
return this._date;
}
set language(val) {
if (val) {
this._language = val;
this.days_names = translatedDayNames(this._language);
this.months_names = translatedMonthNames(this._language);
this.requestUpdate();
}
}
get language() {
return this._language;
}
static get styles() {
return [elevationStyles];
}
render() {
// language=HTML
return html `
<style>
:host {
display: block;
background-color: white;
width: 312px;
-webkit-font-smoothing: antialiased;
font-family: var(--sl-font-sans, 'Roboto, Noto, sans-serif');
border: 1px solid #eee;
--my-elem-primary: var(--primary-color, #3acfe3);
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
text-rendering: optimizeLegibility !important;
-webkit-font-smoothing: antialiased !important;
position: absolute;
z-index: 1;
border-radius: 0.5rem;
}
/*Animation while showing next or previous months*/
.slide-right {
animation: slide-right 0.4s linear forwards;
visibility: hidden;
-webkit-animation: slide-right 0.4s linear forwards;
}
.slide-left {
animation: slide-left 0.4s linear forwards;
visibility: hidden;
-webkit-animation: slide-left 0.4s linear forwards;
}
/* Animation while showing years and months*/
.scale-up {
animation: scale-up 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
-webkit-animation: scale-up 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
@-webkit-keyframes scale-up {
0% {
-webkit-transform: scale(0);
transform: scale(0);
}
100% {
-webkit-transform: scale(1, 1);
transform: scale(1, 1);
}
}
@-webkit-keyframes slide-left {
0% {
-webkit-transform: translateX(0%);
transform: translateX(0%);
visibility: hidden;
}
30% {
-webkit-transform: translateX(3%);
transform: translateX(3%);
visibility: hidden;
}
60% {
-webkit-transform: translateX(-3%);
transform: translateX(-3%);
visibility: hidden;
}
90% {
-webkit-transform: translateX(0%);
transform: translateX(0%);
visibility: visible;
}
100% {
-webkit-transform: translateX(0%);
visibility: visible;
}
}
@-webkit-keyframes slide-right {
0% {
-webkit-transform: translateX(0%);
transform: translateX(0%);
visibility: hidden;
}
30% {
-webkit-transform: translateX(-3%);
transform: translateX(-3%);
visibility: hidden;
}
60% {
-webkit-transform: translateX(3%);
transform: translateX(3%);
visibility: hidden;
}
90% {
-webkit-transform: translateX(0%);
transform: translateX(0%);
visibility: visible;
}
100% {
-webkit-transform: translateX(0%);
visibility: visible;
}
}
/* Decorate scroll bar for years list*/
.flex-wrap::-webkit-scrollbar-track {
background-clip: padding-box;
border: solid transparent;
border-width: 0 0 0 4px;
}
.flex-wrap::-webkit-scrollbar {
width: 6px;
}
#yearList {
height: 220px;
}
.flex-wrap::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
/* color the .dateSticker on selected */
.dateSticker.selected {
background: var(--my-elem-primary);
color: #fff;
}
.dateSticker.today {
border: solid 1px var(--my-elem-primary);
margin-top: -0.5px !important;
}
/* disabled .dateSticker color and pointer */
.dateSticker[disabled] {
color: #9d9898;
cursor: not-allowed !important;
}
/* change border on hover on years in years list and months in months list */
.dateItem:hover {
transition: border 0.3s ease;
border: 1px solid #eee;
}
.flex-horizontal,
.dates {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.flexchild {
display: flex;
flex-direction: row;
flex: 1;
justify-content: left;
align-items: center;
padding: 0 5px 0 5px;
color: #474e54;
font-size: var(--etools-font-size-16, 16px);
font-weight: 700;
}
.flexchild > div > etools-icon-button {
margin-inline-start: -4px;
}
.dayNames,
.dates {
justify-content: space-between;
}
.dates .dateSticker {
border-radius: 50%;
padding: 4px;
text-align: center;
width: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 24px;
cursor: pointer;
margin: 1px;
font-size: var(--etools-font-size-14, 14px);
}
.notextselect,
.dateSticker {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.dayNames > div {
padding: 8px 12px;
font-size: var(--etools-font-size-12, 12px);
color: #474e54;
font-weight: 700;
}
.flex-wrap {
display: flex;
flex-direction: row;
flex-wrap: wrap;
height: 100%;
align-items: center;
overflow: auto;
justify-content: center;
overflow: auto;
}
.monthwrap {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.monthwrap div {
margin: 4px;
}
.dateItem {
border: 1px solid transparent;
cursor: pointer;
font-size: var(--etools-font-size-15, 15px);
padding: 8px 12px;
border-radius: 4px;
}
.flex-wrap div {
padding: 8px;
margin: 3px;
font-size: var(--etools-font-size-13, 13px);
}
.pageContainer {
padding-bottom: 14px;
}
.headerContainer {
cursor: pointer;
padding: 4px;
width: 50%;
display: flex;
align-items: center;
}
.swipePanel etools-icon-button {
margin: 2px;
border: solid 2px #dddddd;
border-radius: 50%;
--etools-icon-font-size: var(--etools-font-size-18, 18px);
}
#dateContainer {
height: 195px;
}
.swipePanel etools-icon-button[focused] {
background: #dddddd;
}
.elevation[elevation='1'] {
height: 100%;
padding: 0 12px 0 12px;
border-radius: 0.5rem;
}
.container.menu {
padding-top: 10px;
margin-bottom: 10px;
border-bottom: solid 1px var(--primary-color);
}
#headerYear:focus {
outline: 1px solid white;
}
#headerMonth:focus {
outline: 1px solid white;
}
.day:focus:not(:focus-visible) {
outline: 0;
}
.day:focus-visible {
outline: 1px solid black;
}
</style>
<!-- Main header date,month,year are compund binded to selected date -->
<div class="elevation" elevation="1">
<div>
<!-- header with left, right icon and present viewing month -->
<div class="container menu flex-horizontal">
<div class="flexchild">
<div
id="headerMonth"
class="headerContainer notextselect"
type="monthsList"
="${this.activateOnEnterAndSpace}"
="${this._show}"
>
${this.monthFormat}
<etools-icon-button
label="expand months"
type="monthsList"
="${this._show}"
name="expand-more"
></etools-icon-button>
</div>
<div
id="headerYear"
class="headerContainer notextselect"
type="yearList"
="${this.activateOnEnterAndSpace}"
="${this._show}"
>
${this.yearFormat}
<etools-icon-button
label="expand years"
type="yearList"
="${this._show}"
name="expand-more"
></etools-icon-button>
</div>
</div>
<div class="swipePanel">
<etools-icon-button
label="previous month"
="${this._swipePrevMonth}"
name="chevron-left"
></etools-icon-button>
<etools-icon-button
label="next month"
="${this._swipeNextMonth}"
name="chevron-right"
="${this.onSwipeNextKeyDown}"
></etools-icon-button>
</div>
</div>
<!-- .pageContainer contains calendar, months list and years list -->
<div class="pageContainer">
<!-- years list -->
<div id="yearList" class="page" style="display:none;">
<div class="flex-wrap">
${(this.years || []).map((item) => html `
<div
class="dateItem notextselect yearItem"
tabindex="0"
.item=${item}
="${this.activateOnEnterAndSpace}"
="${this._setYear}"
>
${item}
</div>
`)}
</div>
</div>
<!-- months list -->
<div id="monthsList" class="page" style="display:none;">
<div class="monthwrap">
${this.months_names.map((item) => html `
<div
class="dateItem notextselect"
tabindex="0"
.item=${item}
="${this.activateOnEnterAndSpace}"
="${this._setMonth}"
>
${item}
</div>
`)}
</div>
</div>
<!-- calendar -->
<div class="container page" id="calendarContent">
<div id="mainContent">
<div class="container flex-horizontal dayNames">
<div>${translatedDaysFirstLetter(this.language, 0)}</div>
<div>${translatedDaysFirstLetter(this.language, 1)}</div>
<div>${translatedDaysFirstLetter(this.language, 2)}</div>
<div>${translatedDaysFirstLetter(this.language, 3)}</div>
<div>${translatedDaysFirstLetter(this.language, 4)}</div>
<div>${translatedDaysFirstLetter(this.language, 5)}</div>
<div>${translatedDaysFirstLetter(this.language, 6)}</div>
</div>
<div id="dateContainer">
<!-- separator splits calendar into 6 rows -->
${this.separator.map((row) => html ` <div class="dates">${this._getDays(row).map((day) => this._getDayHTML(day))}</div> `)}
</div>
</div>
</div>
</div>
<slot name="actions"></slot>
</div>
</div>
`;
}
updated(changedProperties) {
if ((changedProperties.has('minDate') && changedProperties.get('minDate')) ||
(changedProperties.has('maxDate') && changedProperties.get('maxDate'))) {
this.generateTable();
}
}
get mainContent() {
if (!this._mainContent) {
this._mainContent = this.shadowRoot.querySelector('#mainContent');
}
return this._mainContent;
}
_getSelectedYear() {
return this.date ? this.date.getFullYear() : this._getCurrentDate().getFullYear();
}
_getSelectedMonth() {
return this.date ? this.date.getMonth() : this._getCurrentDate().getMonth();
}
_getSelectedDay() {
return this.date ? this.date.getDate() : null;
}
_getCurrentDate() {
return new Date();
}
get monthFormat() {
return this.months_names[this.currentMonth];
}
get yearFormat() {
return this.currentYear;
}
constructor() {
super();
this.tmpDate = null;
this.cf = null;
this.days_names = [];
this.current_page = '';
this.tmpObject = null;
this.multiple = [];
this.years = [];
this.minDate = null;
this.maxDate = null;
this.disabledDays = [];
this.multiSelect = null;
this.days = [];
this.separator = [0, 1, 2, 3, 4, 5];
this.disabledWeekDay = [];
this.format = 'YYYY-MM-DD';
this.hideHeader = false;
this._language = window.EtoolsLanguage || 'en';
this.initializeProperties();
}
initializeProperties() {
this.days_names = translatedDayNames(this.language);
this.months_names = translatedMonthNames(this.language);
this.handleLanguageChange = this.handleLanguageChange.bind(this);
}
handleLanguageChange(e) {
this.language = e.detail.language;
}
onSwipeNextKeyDown(event) {
if (event.key === 'Tab' && !event.shiftKey) {
// when using tab navigation on years select by default current year
const elSelectedYear = this._scrollToSelectedYear();
if (elSelectedYear) {
setTimeout(() => {
elSelectedYear.focus();
}, 10);
}
}
}
activateOnEnterAndSpace(event) {
if ((event.key === ' ' && !event.ctrlKey) || event.key === 'Enter') {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
event.target.click();
return false;
}
return true;
}
focusOnHeaderYear() {
this.shadowRoot.querySelector('#headerYear').focus();
}
// to get number of days in a month
monthDays(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}
connectedCallback() {
// generate 6 x 7 table
super.connectedCallback(); // for 2.0 class-based elements only
document.addEventListener('language-changed', this.handleLanguageChange);
this.days_names = translatedDayNames(this.language);
this.months_names = translatedMonthNames(this.language);
this._animationEvent = this._whichAnimationEnd();
this.multiple.push(this._getSelectedDay() + ',' + this._getSelectedMonth() + ',' + this._getSelectedYear());
this.currentYear = this._getSelectedYear();
this.currentMonth = this._getSelectedMonth();
this.todayYear = this._getCurrentDate().getFullYear();
this.todayMonth = this._getCurrentDate().getMonth();
this.todayDay = this._getCurrentDate().getDate();
this.generateTable();
// push into years list
if (!!this.maxDate && !!this.minDate) {
this._generateYears(this.minDate.getFullYear(), this.maxDate.getFullYear());
}
else {
this._generateYears(this.currentYear - 10, this.currentYear + 10);
}
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener('language-changed', this.handleLanguageChange);
}
generateTable() {
// clone into tmpDate
this.tmpDate = new Date(this.currentYear, this.currentMonth, 1);
// tmpArray contains 6x7(42) items
let tmpArray = [];
this.cf = 0;
// fill with empty cells
for (let i = 0; i < this.tmpDate.getDay(); i++) {
tmpArray.push({ text: '', isDisabled: false, i: this.cf++ });
}
// fill days and check disable dates
for (let i = 1; i <= this.monthDays(this.tmpDate); i++) {
this.tmpDate.setDate(i);
if ((!!this.minDate && this.tmpDate < this.minDate) ||
(!!this.maxDate && this.tmpDate > this.maxDate) ||
this.disabledWeekDay.indexOf(this.days_names[this.tmpDate.getDay()]) != -1 ||
this.disabledDays.indexOf(i) != -1) {
tmpArray.push({ text: i, isDisabled: true, i: this.cf++ });
}
else {
tmpArray.push({ text: i, isDisabled: false, i: this.cf++ });
}
}
// fill remaining empty cells
this.cf = tmpArray.length > 35 ? 42 - tmpArray.length : 34 - tmpArray.length;
for (let j = 0; j <= this.cf; j++) {
tmpArray.push({ text: '' });
}
this.days = tmpArray;
tmpArray = [];
}
_getDayClass(dayText) {
if (this.multiSelect != null) {
if (this.multiple.indexOf(dayText + ',' + this.currentMonth + ',' + this.currentYear) > -1) {
return 'dateSticker selected';
}
}
if (this._getSelectedDay() == dayText &&
this._getSelectedMonth() == this.currentMonth &&
this._getSelectedYear() == this.currentYear) {
return 'dateSticker selected';
}
if (dayText == this.todayDay && this.currentMonth == this.todayMonth && this.currentYear == this.todayYear) {
return 'dateSticker today';
}
return 'dateSticker';
}
_setDate(e) {
const f = e.target.day;
if (f.text != '' && !f.isDisabled) {
if (this.multiSelect != null) {
if (this.multiSelect.consequent) {
this.multiple = [];
this.cf = f.i;
this.multiple.push(f.text + ',' + this.currentMonth + ',' + this.currentYear);
for (let j = 1; this.multiple.length < this.multiSelect.max; j++) {
this.tmpDate = new Date(this.currentYear, this.currentMonth, f.text + j);
if ((!!this.minDate && this.tmpDate <= this.minDate) ||
(!!this.maxDate && this.tmpDate >= this.maxDate) ||
this.disabledWeekDay.indexOf(this.days_names[this.tmpDate.getDay()]) != -1 ||
this.disabledDays.indexOf(this.tmpDate.getDate()) != -1) {
// Nothing to do
}
else {
this.multiple.push(this.tmpDate.getDate() + ',' + this.tmpDate.getMonth() + ',' + this.tmpDate.getFullYear());
}
}
}
else {
this.cf = this.multiple.indexOf(f.text + ',' + this.currentMonth + ',' + this.currentYear);
if (this.cf < 0) {
this.multiple.push(f.text + ',' + this.currentMonth + ',' + this.currentYear);
}
else {
e.target.classList.remove('selected');
this.multiple.splice(this.cf, 1);
this.triggerEvent('multiselect', this.multiple);
return;
}
if (this.multiple.length > this.multiSelect.max) {
this.multiple.shift();
}
}
this.triggerEvent('multiselect', this.multiple);
}
this.date = new Date(this.currentYear, this.currentMonth, f.text);
this.prettyDate = dateLib(this.date).format(this.format);
}
}
_keyPressSelect(e) {
if (e.which === 13) {
this._setDate(e);
}
}
triggerEvent(e, data) {
this.dispatchEvent(new CustomEvent(e, { detail: { dates: data } }));
}
_setYear(e) {
this.currentYear = e.target.item;
this.generateTable();
this.current_page = 'calendarContent';
this.pagination();
}
_setMonth(e) {
this.currentMonth = this.months_names.indexOf(e.target.item);
this.generateTable();
this.current_page = 'calendarContent';
this.pagination();
}
_show(e) {
this.current_page = e.target.attributes.type.value;
setTimeout(() => {
this._scrollToSelectedYear();
}, 50);
this.pagination();
}
_scrollToSelectedYear() {
let selectedYearElem;
const yearList = this.shadowRoot.querySelector('#yearList');
if (yearList && yearList.style.display !== 'none') {
const list = this.shadowRoot.querySelectorAll('.yearItem');
selectedYearElem = Array.from(list).find((el) => Number(el.innerText) === this._getSelectedYear());
if (selectedYearElem) {
selectedYearElem.scrollIntoView({ block: 'center' });
}
}
return selectedYearElem;
}
pagination() {
let pages = this.shadowRoot.querySelectorAll('.page');
for (let i = 0; i < pages.length; i++) {
pages[i].style.display = 'none';
}
this.tmpObject = this.shadowRoot.querySelector('#' + this.current_page);
this.tmpObject.style.display = 'block';
this.tmpObject.classList.add('scale-up');
this._once(this._animationEvent, () => {
this.tmpObject.classList.remove('scale-up');
}, this.tmpObject);
pages = null;
}
_generateYears(min, max) {
const tmpArray = [];
for (let i = min; i <= max; i++) {
tmpArray.push(i);
}
this.years = tmpArray;
}
_populate(newDate) {
this.currentMonth = newDate ? newDate.getMonth() : this._getCurrentDate().getMonth();
this.currentYear = newDate ? newDate.getFullYear() : this._getCurrentDate().getFullYear();
this.currentDay = newDate ? newDate.getDay() : null;
this.generateTable();
}
_swipeNextMonth() {
this.mainContent.classList.add('slide-right');
this._once(this._animationEvent, () => {
this.mainContent.classList.remove('slide-right');
}, this.mainContent);
this.changeView(1);
}
changeView(x) {
const tmp = new Date(this.currentYear, this.currentMonth, 1);
tmp.setMonth(this.currentMonth + (typeof x === 'number' ? x : 0));
this.currentMonth = tmp.getMonth();
this.currentYear = tmp.getFullYear();
this.dispatchEvent(new CustomEvent('month-change', { detail: { date: this.tmpDate } }));
this.generateTable();
}
_once(eventName, callback, node) {
function onceCallback() {
node.removeEventListener(eventName, onceCallback);
callback();
}
node.addEventListener(eventName, onceCallback);
}
_swipePrevMonth() {
this.mainContent.classList.add('slide-left');
this._once(this._animationEvent, () => {
this.mainContent.classList.remove('slide-left');
}, this.mainContent);
this.changeView(-1);
}
_getDays(row) {
return this.days.slice(row * 7, row * 7 + 7);
}
_getDayHTML(day) {
const tabIndex = day.text ? 0 : -1;
const cssClass = this._getDayClass(day.text) + (day.text ? ' day' : '');
return html ` <div
="${this._setDate}"
.day=${day}
="${this._keyPressSelect}"
class="${cssClass}"
?disabled="${day.isDisabled}"
tabindex="${tabIndex}"
>
${day.text}
</div>`;
}
_whichAnimationEnd() {
const animations = {
WebkitTransition: 'webkitAnimationEnd',
MozTransition: 'animationend',
OTransition: 'oanimationend oAnimationEnd ',
transition: 'animationend'
};
for (const t in animations) {
if (this.style[t] !== undefined) {
return animations[t];
}
}
}
};
__decorate([
property({
type: Date
})
], CalendarLite.prototype, "date", null);
__decorate([
property({
type: Number
})
], CalendarLite.prototype, "currentMonth", void 0);
__decorate([
property({
type: Number
})
], CalendarLite.prototype, "currentDay", void 0);
__decorate([
property({
type: Date,
reflect: true,
attribute: 'min-date',
converter: {
fromAttribute: (value) => {
if (typeof value === 'string') {
return dateLib(value, controlFormat).toDate();
}
return value;
}
}
})
], CalendarLite.prototype, "minDate", void 0);
__decorate([
property({
type: Date,
reflect: true,
attribute: 'max-date',
converter: {
fromAttribute: (value) => {
if (typeof value === 'string') {
return dateLib(value, controlFormat).toDate();
}
return value;
}
}
})
], CalendarLite.prototype, "maxDate", void 0);
__decorate([
property({
type: Array
})
], CalendarLite.prototype, "disabledDays", void 0);
__decorate([
property({
type: Number
})
], CalendarLite.prototype, "currentYear", void 0);
__decorate([
property({
type: Object
})
], CalendarLite.prototype, "multiSelect", void 0);
__decorate([
property({
type: Array
})
], CalendarLite.prototype, "days", void 0);
__decorate([
property({
type: Array
})
], CalendarLite.prototype, "separator", void 0);
__decorate([
property({
type: Array
})
], CalendarLite.prototype, "disabledWeekDay", void 0);
__decorate([
property({
type: String,
reflect: true,
attribute: 'pretty-date'
})
], CalendarLite.prototype, "prettyDate", void 0);
__decorate([
property({
type: String
})
], CalendarLite.prototype, "format", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
attribute: 'hide-header'
})
], CalendarLite.prototype, "hideHeader", void 0);
__decorate([
property({
type: String
})
], CalendarLite.prototype, "language", null);
CalendarLite = __decorate([
customElement('calendar-lite')
], CalendarLite);
export { CalendarLite };