@progressive-development/pd-calendar
Version:
Webcomponent for calendar
435 lines (416 loc) • 13.8 kB
JavaScript
import { css, LitElement, html } from 'lit';
import { property, state } from 'lit/decorators.js';
import { msg, localized } from '@lit/localize';
import { format } from 'fecha';
import '@progressive-development/pd-icon/pd-icon';
import './pd-year-popup/pd-year-popup.js';
import './pd-calendar-cell/pd-calendar-cell.js';
import { PdYearPopup } from './pd-year-popup/PdYearPopup.js';
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
const TOUCH_MIN_MOVE = 70;
const loadLocales = () => ({
monthNames: [
msg("Januar", { id: "pd.datepicker.month.jan" }),
msg("Februar", { id: "pd.datepicker.month.feb" }),
msg("März", { id: "pd.datepicker.month.mar" }),
msg("April", { id: "pd.datepicker.month.apr" }),
msg("Mai", { id: "pd.datepicker.month.may" }),
msg("Juni", { id: "pd.datepicker.month.jun" }),
msg("Juli", { id: "pd.datepicker.month.jul" }),
msg("August", { id: "pd.datepicker.month.aug" }),
msg("September", { id: "pd.datepicker.month.sep" }),
msg("Oktober", { id: "pd.datepicker.month.oct" }),
msg("November", { id: "pd.datepicker.month.nov" }),
msg("Dezember", { id: "pd.datepicker.month.dec" })
],
weekdaysShort: [
msg("Mo", { id: "pd.datepicker.shortday.mon" }),
msg("Di", { id: "pd.datepicker.shortday.tue" }),
msg("Mi", { id: "pd.datepicker.shortday.wed" }),
msg("Do", { id: "pd.datepicker.shortday.thi" }),
msg("Fr", { id: "pd.datepicker.shortday.fri" }),
msg("Sa", { id: "pd.datepicker.shortday.sat" }),
msg("So", { id: "pd.datepicker.shortday.sun" })
]
});
const LOCALES = loadLocales();
const VIEW_MONTH = 2;
function isToday(date) {
const today = /* @__PURE__ */ new Date();
return !!date && date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
}
function isSelected(date1, date2) {
return !!date1 && !!date2 && date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth() && date1.getFullYear() === date2.getFullYear();
}
let PdCalendar = class extends LitElement {
constructor() {
super(...arguments);
this.selectableDates = false;
this.withYearPopup = [];
this.withWheelNavigation = false;
this.withTouchNavigation = false;
this.showSelection = false;
this.hideWeekend = false;
this.prevMonthConstraint = -1;
this.nextMonthConstraint = -1;
this.data = {};
this.numberClass = "top-left";
this._viewType = VIEW_MONTH;
this._currentDate = /* @__PURE__ */ new Date();
this._wheelDelta = 0;
this._monthName = "";
this._year = 0;
this._numberOfDays = 0;
this._daysFromPreviousMonth = [];
this._currentMonthNavNr = 0;
}
connectedCallback() {
super.connectedCallback();
this._initFromDate(this.refDate ?? /* @__PURE__ */ new Date());
}
update(changedProperties) {
if (changedProperties.has("refDate") && this.refDate) {
this._initFromDate(this.refDate);
}
super.update(changedProperties);
}
render() {
return html`
${this.renderMonthCalendar()}
<slot name="calFooter"></slot>
`;
}
renderMonthCalendar() {
const sizeClass = this._numberOfDays >= 31 && this._daysFromPreviousMonth.length >= 5 ? "max" : "normal";
const days = Array.from({ length: this._numberOfDays }, (_, i) => {
const day = i + 1;
const date = new Date(this._year, this._currentDate.getMonth(), day);
if (this.hideWeekend && [0, 6].includes(date.getDay())) return null;
const key = format(date, "YYYY-MM-DD");
const cellData = this.data ? this.data[key]?.[0] : void 0;
return html`
<pd-calendar-cell
key=${key}
.dayNumber=${day}
.weekDayNumber=${date.getDay()}
.infoTxt=${cellData?.info || ""}
?selectEnabled=${this.selectableDates || !!cellData?.info}
?special=${cellData?.special ?? false}
.numberClass=${this.numberClass}
?today=${this.selectableDates && isToday(date)}
?selected=${this.showSelection && isSelected(this.refDate, date)}
></pd-calendar-cell>
`;
});
return html`
<div class="layout-container">
<div class="header">
<div class="header-main">
<div class="icon-container">
<pd-icon
class="arrow"
icon="previousArrow"
activeIcon
@click=${this._previousMonth}
></pd-icon>
<pd-icon
class="arrow"
icon="nextArrow"
activeIcon
@click=${this._nextMonth}
></pd-icon>
</div>
<div id="titleContentId" class="content-title">
${this._monthName}
${this.withYearPopup.length > 0 ? html`<span
class="year-popup-link"
@click=${this._openYearPopup}
>${this._year}</span
>` : this._year}
</div>
</div>
</div>
<div
class="content grid-container ${sizeClass}"
@wheel=${this._wheelEvent}
@touchstart=${this._touchStartEvent}
@touchend=${this._touchEndEvent}
>
${this._getWeekDays().map(
(day) => html`<div class="title-week-day">${day}</div>`
)}
${this._daysFromPreviousMonth.map(
() => html`<div class="cell cell-empty"></div>`
)}
${days}
</div>
</div>
`;
}
_openYearPopup() {
const popup = new PdYearPopup();
popup.yearSelection = this.withYearPopup;
this.shadowRoot?.getElementById("titleContentId")?.appendChild(popup);
popup.addEventListener("abort-year-selection", () => {
this.shadowRoot?.getElementById("titleContentId")?.removeChild(popup);
});
popup.addEventListener("change-year-selection", (e) => {
const year = e.detail.year;
const newDate = new Date(this._currentDate);
newDate.setFullYear(year);
this.dispatchEvent(
new CustomEvent("change-month", { detail: { newDate } })
);
this._initFromDate(newDate);
this.shadowRoot?.getElementById("titleContentId")?.removeChild(popup);
});
}
_getWeekDays() {
return this.hideWeekend ? LOCALES.weekdaysShort.slice(0, 5) : LOCALES.weekdaysShort;
}
_nextMonth() {
if (this._checkNextMonthConstraint()) {
const next = new Date(
this._currentDate.getFullYear(),
this._currentDate.getMonth() + 1,
1
);
this.dispatchEvent(
new CustomEvent("change-month", {
detail: { newDate: next, next: true }
})
);
this._currentMonthNavNr += 1;
this._initFromDate(next);
}
}
_previousMonth() {
if (this._checkPrevMonthConstraint()) {
const prev = new Date(
this._currentDate.getFullYear(),
this._currentDate.getMonth() - 1,
1
);
this.dispatchEvent(
new CustomEvent("change-month", {
detail: { newDate: prev, prev: true }
})
);
this._currentMonthNavNr -= 1;
this._initFromDate(prev);
}
}
_wheelEvent(e) {
if (this.withWheelNavigation) {
this._wheelDelta += e.deltaY;
if (this._wheelDelta > 360) {
this._wheelDelta = 0;
this._nextMonth();
} else if (this._wheelDelta < -360) {
this._wheelDelta = 0;
this._previousMonth();
}
}
e.preventDefault();
e.stopPropagation();
}
_touchStartEvent(e) {
if (this.withTouchNavigation) {
this._wheelDelta = e.changedTouches[0].screenX;
}
e.stopPropagation();
}
_touchEndEvent(e) {
if (this.withTouchNavigation) {
const diff = this._wheelDelta - e.changedTouches[0].screenX;
if (diff < -TOUCH_MIN_MOVE) {
this._nextMonth();
} else if (diff > TOUCH_MIN_MOVE) {
this._previousMonth();
}
}
e.stopPropagation();
}
_initFromDate(date) {
this._monthName = date.toLocaleString("default", { month: "long" });
this._year = date.getFullYear();
this._currentDate = date;
this._daysFromPreviousMonth = PdCalendar._getPreviousMonthDays(date);
const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
this._numberOfDays = lastDay.getDate();
}
static _getPreviousMonthDays(date) {
const start = new Date(date.getFullYear(), date.getMonth(), 1);
const missing = start.getDay() > 0 ? Math.abs(1 - start.getDay()) : 6;
return Array.from({ length: missing }, (_, i) => {
const d = new Date(start);
d.setDate(start.getDate() - (missing - i));
return d;
});
}
_checkNextMonthConstraint() {
return this.nextMonthConstraint === -1 || this.nextMonthConstraint > this._currentMonthNavNr;
}
_checkPrevMonthConstraint() {
return this.prevMonthConstraint === -1 || this._currentMonthNavNr > 0 || this.prevMonthConstraint > this._currentMonthNavNr * -1;
}
};
PdCalendar.styles = [
css`
:host {
--my-cell-height: var(--pd-calendar-cell-height, 70px);
display: block;
/*padding: 8px;
width: 100vw;
height: 100vh;*/
}
:host([hideWeekend]) .grid-container {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
/* Layout Grid for the Calendar Component => Not really needed at the moment, more a test */
.layout-container {
width: var(--pd-calendar-width, 100%);
min-width: 220px;
height: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 40px auto;
gap: 1px;
grid-template-areas:
"header header header"
"content content content";
}
/* Grid Area positions for layout container above */
.header {
grid-area: header;
position: relative;
font-family: var(--pd-default-font-title-family);
color: var(--pd-default-font-title-col);
display: flex;
justify-content: left;
}
.header-main {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.content {
grid-area: content;
}
.footer {
grid-area: footer;
}
/* Grid definition for calendar day items */
.grid-container {
position: relative;
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 3px;
}
.grid-container.max {
grid-template-rows: minmax(10px, 30px) repeat(
6,
minmax(var(--my-cell-height), 1fr)
);
}
.grid-container.normal {
grid-template-rows: minmax(10px, 30px) repeat(
5,
minmax(var(--my-cell-height), 1fr)
);
}
.title-week-day {
display: flex;
align-items: center;
justify-content: center;
background-color: var(
--pd-calendar-week-title-bg-col,
var(--pd-default-dark-col)
);
font-size: var(--pd-calendar-weekday-title-font-size, 1em);
font-weight: bold;
color: var(--pd-calendar-week-title-font-col, var(--pd-default-bg-col));
font-family: var(--pd-default-font-title-family);
}
.content-title {
position: relative;
font-size: var(--pd-calendar-title-font-size, 1.2em);
font-weight: bold;
}
.icon-container {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
}
.arrow {
cursor: pointer;
--pd-icon-size: var(--pd-calendar-title-icon-size, 1.2em);
--pd-icon-bg-col-hover: lightgrey;
}
.year-popup-link {
color: var(--pd-default-font-link-col);
text-decoration: underline;
}
.year-popup-link:hover {
cursor: pointer;
color: var(--pd-default-hover-col);
}
`
];
__decorateClass([
property({ type: Object })
], PdCalendar.prototype, "refDate", 2);
__decorateClass([
property({ type: Boolean })
], PdCalendar.prototype, "selectableDates", 2);
__decorateClass([
property({ type: Array })
], PdCalendar.prototype, "withYearPopup", 2);
__decorateClass([
property({ type: Boolean })
], PdCalendar.prototype, "withWheelNavigation", 2);
__decorateClass([
property({ type: Boolean })
], PdCalendar.prototype, "withTouchNavigation", 2);
__decorateClass([
property({ type: Boolean })
], PdCalendar.prototype, "showSelection", 2);
__decorateClass([
property({ type: Boolean, reflect: true })
], PdCalendar.prototype, "hideWeekend", 2);
__decorateClass([
property({ type: Number })
], PdCalendar.prototype, "prevMonthConstraint", 2);
__decorateClass([
property({ type: Number })
], PdCalendar.prototype, "nextMonthConstraint", 2);
__decorateClass([
property({ type: Object })
], PdCalendar.prototype, "data", 2);
__decorateClass([
property({ type: String })
], PdCalendar.prototype, "numberClass", 2);
__decorateClass([
state()
], PdCalendar.prototype, "_viewType", 2);
__decorateClass([
state()
], PdCalendar.prototype, "_currentDate", 2);
__decorateClass([
state()
], PdCalendar.prototype, "_wheelDelta", 2);
PdCalendar = __decorateClass([
localized()
], PdCalendar);
export { PdCalendar };