UNPKG

@progressive-development/pd-calendar

Version:

Webcomponent for calendar

435 lines (416 loc) 13.8 kB
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 };