@progressive-development/pd-calendar
Version:
Webcomponent for calendar
495 lines (483 loc) • 15.2 kB
JavaScript
import { css, html } from 'lit';
import { localized, msg } from '@lit/localize';
import { property, state } from 'lit/decorators.js';
import { parse, format } from 'date-fns';
import { PdBaseInputElement } from '@progressive-development/pd-forms';
import { pdIcons } from '@progressive-development/pd-icon';
import '@progressive-development/pd-forms/pd-input';
import '@progressive-development/pd-forms/pd-button';
import '@progressive-development/pd-forms/pd-select';
import '../pd-calendar.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 UNDEF = "UNDEF";
const INPUT_TYPE_DATE = 7;
const DEFAULT_FORMAT = "yyyy-MM-dd";
const DATE_TIMES = [
{ name: "Uhrzeit auswählen", value: UNDEF },
{ name: "07:00", value: "07:00" },
{ name: "07:30", value: "07:30" },
{ name: "08:00", value: "08:00" },
{ name: "08:30", value: "08:30" },
{ name: "09:00", value: "09:00" },
{ name: "09:30", value: "09:30" },
{ name: "10:00", value: "10:00" },
{ name: "10:30", value: "10:30" },
{ name: "11:00", value: "11:00" },
{ name: "11:30", value: "11:30" },
{ name: "12:00", value: "12:00" },
{ name: "12:30", value: "12:30" },
{ name: "13:00", value: "13:00" },
{ name: "13:30", value: "13:30" },
{ name: "14:00", value: "14:00" },
{ name: "14:30", value: "14:30" },
{ name: "15:00", value: "15:00" },
{ name: "15:30", value: "15:30" },
{ name: "16:00", value: "16:00" },
{ name: "16:30", value: "16:30" },
{ name: "17:00", value: "17:00" },
{ name: "17:30", value: "17:30" },
{ name: "18:00", value: "18:00" },
{ name: "18:30", value: "18:30" },
{ name: "19:00", value: "19:00" },
{ name: "19:30", value: "19:30" }
];
function getDateRangeValue(start, end, formatStr = DEFAULT_FORMAT) {
return `${start ? format(start, formatStr) : "--"} - ${end ? format(end, formatStr) : "--"}`;
}
function getDateTimeValue(date, time, formatStr = DEFAULT_FORMAT) {
return `${date ? format(date, formatStr) : ""} ${time && time !== UNDEF ? time : ""}`;
}
function getDataForDaysBetween(start, end) {
const result = {};
let from = new Date(start);
let to = new Date(end);
if (from > to) [from, to] = [to, from];
while (from <= to) {
result[format(from, DEFAULT_FORMAT)] = [{ special: true }];
from.setDate(from.getDate() + 1);
}
return result;
}
let PdDatepicker = class extends PdBaseInputElement {
constructor() {
super();
this.dateRange = false;
this.hideToday = false;
this.withYearPopup = [];
this.withTime = false;
this._showCalendar = false;
this._timeSelection = UNDEF;
this._calendarPosition = "bottom";
this._inputType = INPUT_TYPE_DATE;
this._validators = [this._requiredDateTimeValidator.bind(this)];
}
_requiredDateTimeValidator() {
return this.required && !this._startDateValue || this.dateRange && this._startDateValue && !this._endDateValue || this.withTime && this._startDateValue && this._timeSelection === UNDEF ? msg("Eingabe unvollständig", {
id: "pd.form.field.selectDateTimeRequired"
}) : null;
}
update(changedProps) {
if (changedProps.has("initialDate") && this.initialDate) {
this._prepareFieldsFromInitDate();
this._updateInputField(false, void 0);
}
super.update(changedProps);
}
render() {
return html`
<pd-input
id="datePickerId"
.label="${this.label}"
.placeHolder="${this.placeHolder}"
.icon="${pdIcons.ICON_DATE}"
.initValue="${this._value}"
?readonly=${true}
?required="${this.required}"
?disabled="${this.disabled}"
icon-right
="${this._inputClick}"
-form-element-change="${this._innerFormChange}"
-form-element-blur="${this._innerBlur}"
-form-element-focus="${this._innerFocus}"
-form-element-register="${this._innerRegister}"
-pressed="${this._innerEnter}"
>
<div slot="icon-right">
<slot name="icon-right"></slot>
</div>
></pd-input
>
${this._renderErrorMsg()}
<div class="calendar-small ${this._calendarPosition}">
<pd-calendar
.withYearPopup="${this.withYearPopup}"
.refDate="${this._startDateValue}"
.data="${this._rangeSelection || {}}"
numberClass="center"
selectableDates
showSelection
prevMonthConstraint="-1"
nextMonthConstraint="-1"
-date="${this._triggerCalendarDateSelected}"
-enter-date="${this._triggerHoverCalculateRange}"
></pd-calendar>
${this.withTime ? html`
<pd-select
class="date-time-select"
id="dateTimeSelectId"
.values="${DATE_TIMES}"
label="${msg("Uhrzeit", { id: "pd.datepicker.time" })}"
initValue="${this._timeSelection}"
required
-form-element-change="${(e) => {
this._timeSelection = e.detail.value;
e.stopPropagation();
}}"
-form-element-blur="${this._innerBlur}"
-form-element-focus="${this._innerFocus}"
-form-element-register="${this._innerRegister}"
-pressed="${this._innerEnter}"
></pd-select>
` : ""}
<div class="bottom-buttons">
${!this.hideToday ? html`<pd-button
text="${msg("Heute", { id: "pd.datepicker.today" })}"
-clicked="${this._triggerSetTodayButton}"
></pd-button>` : ""}
<pd-button
text="${msg("Reset", { id: "pd.datepicker.reset" })}"
-clicked="${this._triggerResetButton}"
></pd-button>
${this.withTime ? html`
<pd-button
id="buttonSetValueId"
text="Ok"
?disabled="${!this._startDateValue || this._timeSelection === UNDEF}"
-clicked="${this._triggerSetValueButton}"
></pd-button>
` : ""}
</div>
</div>
`;
}
/**
* Getter for outside call.
*
* @returns obj: { start?: Date; end?: Date } - the prepared date values.
*/
get dateValues() {
const start = this.withTime && this._startDateValue ? parse(
`${format(this._startDateValue, "yyyy-MM-dd")} ${this._timeSelection}`,
"yyyy-MM-dd HH:mm",
/* @__PURE__ */ new Date()
) : this._startDateValue;
return { start, end: this._endDateValue };
}
reset() {
if (this.initialDate) {
this._prepareFieldsFromInitDate();
} else {
this._clearFields();
}
super.reset();
}
clear() {
this._clearFields();
this._updateInputField(true, void 0);
}
/**
* @returns formated value from current field selections.
*/
_getInitialValue() {
return this._formatDateValue();
}
/**
* @returns parsed date
*/
_getParsedValue() {
return this.dateValues;
}
/**
* Init _startDateValue and _timeSelection from initial date.
*/
_prepareFieldsFromInitDate() {
if (this.initialDate) {
this._startDateValue = new Date(
this.initialDate.getFullYear(),
this.initialDate.getMonth(),
this.initialDate.getDate()
);
if (this.withTime) {
this._timeSelection = format(this.initialDate, "HH:mm");
}
}
}
/**
* Clear _startDateValue and _timeSelection.
*/
_clearFields() {
this._startDateValue = void 0;
this._endDateValue = void 0;
this._rangeSelection = void 0;
this._timeSelection = UNDEF;
}
/**
* Update element value from input fields.
*/
_updateInputField(dispatchEvent, e) {
this._handleChangedValue(
this._formatDateValue(),
e,
dispatchEvent,
false,
void 0
);
}
/**
* Toogle view calendar on click into pd-input field.
*/
_inputClick() {
const input = this.shadowRoot?.getElementById("datePickerId");
if (input) {
const rect = input.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
this._calendarPosition = spaceBelow < 300 && spaceAbove > spaceBelow ? "top" : "bottom";
this._showCalendar = !this._showCalendar;
setTimeout(() => {
this.shadowRoot?.querySelector(".calendar-small")?.scrollIntoView({ behavior: "smooth", block: "nearest" });
}, 0);
}
}
/**
* Triggered by date selection in small calendar.
* Set internal values depending on daterange configuration.
*
* @param e Event from small calendar.
*/
_triggerCalendarDateSelected(e) {
const date = e.detail.date;
if (this.dateRange) {
if (!this._startDateValue) {
this._startDateValue = date;
this._endDateValue = void 0;
} else if (!this._endDateValue) {
if (date.getTime() < this._startDateValue.getTime()) {
this._endDateValue = this._startDateValue;
this._startDateValue = date;
} else {
this._endDateValue = date;
}
this._showCalendar = false;
} else {
this._startDateValue = date;
this._endDateValue = void 0;
}
} else {
this._startDateValue = date;
if (this.withTime) {
const el = this.shadowRoot?.getElementById(
this._timeSelection === UNDEF ? "dateTimeSelectId" : "buttonSetValueId"
);
el?.focus();
} else {
this._showCalendar = false;
}
}
if (!this._showCalendar) {
this._updateInputField(true, e);
}
e.stopPropagation();
}
/**
* Triggered by Today button in small calendar view.
* Set calendar date to today.
*/
_triggerSetTodayButton(e) {
this._startDateValue = /* @__PURE__ */ new Date();
if (this.dateRange) {
this._endDateValue = void 0;
this._rangeSelection = void 0;
} else {
this._showCalendar = this.withTime;
}
this._updateInputField(!this._showCalendar, e);
e.stopPropagation();
}
/**
* Triggered by Reset button in small calendar view.
* Reset selected values to origin state (initaldate or empty).
*/
_triggerResetButton(e) {
this.reset();
this._showCalendar = false;
this.shadowRoot?.getElementById("datePickerId")?.reset();
e.stopPropagation();
}
/**
* Triggered by ok button in small calendar view.
* Set selected value and close calendar.
*/
_triggerSetValueButton(e) {
this._updateInputField(true, e);
this._showCalendar = false;
e.stopPropagation();
}
/**
* Calculate range of dates between two dates on mouse over.
* Only needed for date range visuell selection in small calendar.
*
* @param e
*/
_triggerHoverCalculateRange(e) {
if (this._startDateValue && this.dateRange && e.detail.date && !this._endDateValue) {
this._rangeSelection = getDataForDaysBetween(
this._startDateValue,
e.detail.date
);
}
}
/**
* Format date value from _startDateValue and _startDateValue and _timeSelection.
* Depending on daterange and withtime configuration.
*
* @returns string with date value
*/
_formatDateValue() {
if (!this._startDateValue && !this._endDateValue && this._timeSelection === UNDEF)
return "";
return this.dateRange ? getDateRangeValue(
this._startDateValue,
this._endDateValue,
this.dateFormat
) : getDateTimeValue(
this._startDateValue,
this._timeSelection,
this.dateFormat
);
}
// ###### Start Inner Element Events
_innerEnter(e) {
e.stopPropagation();
}
_innerRegister(e) {
e.stopPropagation();
}
_innerUnRegister(e) {
e.stopPropagation();
}
_innerFocus(e) {
e.stopPropagation();
}
_innerBlur(e) {
e.stopPropagation();
}
_innerFormChange(e) {
e.stopPropagation();
}
};
PdDatepicker.styles = [
PdBaseInputElement.styles,
css`
:host {
position: relative;
}
:host([disabled]) {
pointer-events: none;
}
:host([_showCalendar]) .calendar-small {
visibility: visible;
opacity: 1;
}
.calendar-small {
position: absolute;
background-color: white;
visibility: hidden;
z-index: 1000;
box-shadow: var(--pd-menu-shadow, 3px 3px 5px grey);
transition: 0.5s ease-out;
transform: translateY(0);
opacity: 0;
padding: 0.5em 1em 0.5em 0.2em;
--pd-calendar-width: var(--pd-input-field-width, 250px);
--pd-calendar-cell-height: 30px;
--pd-calendar-week-title-bg-col: white;
--pd-calendar-week-title-font-col: #0a3a48;
--pd-calendar-cell-selectable-shadow: 0;
--pd-calendar-cell-day-free-bg-col: white;
--pd-calendar-cell-day-info-free-col: #0a3a48;
--pd-calendar-title-font-size: 20px;
--pd-calendar-title-icon-size: 20px;
--pd-calendar-weekday-title-font-size: 15px;
--pd-calendar-number-font-size: 15px;
}
.calendar-small.bottom {
top: 100%; /* Unter dem Input-Feld */
}
.calendar-small.top {
bottom: 100%; /* Über dem Input-Feld */
}
.bottom-buttons {
display: flex;
--pd-button-width: 78px;
}
`
];
__decorateClass([
property({ type: String })
], PdDatepicker.prototype, "locale", 2);
__decorateClass([
property({ type: Object })
], PdDatepicker.prototype, "initialDate", 2);
__decorateClass([
property({ type: String })
], PdDatepicker.prototype, "dateFormat", 2);
__decorateClass([
property({ type: Boolean })
], PdDatepicker.prototype, "dateRange", 2);
__decorateClass([
property({ type: Boolean })
], PdDatepicker.prototype, "hideToday", 2);
__decorateClass([
property({ type: Array })
], PdDatepicker.prototype, "withYearPopup", 2);
__decorateClass([
property({ type: String })
], PdDatepicker.prototype, "placeHolderTime", 2);
__decorateClass([
property({ type: Boolean })
], PdDatepicker.prototype, "withTime", 2);
__decorateClass([
property({ type: String })
], PdDatepicker.prototype, "min", 2);
__decorateClass([
property({ type: String })
], PdDatepicker.prototype, "max", 2);
__decorateClass([
property({ type: Boolean, reflect: true })
], PdDatepicker.prototype, "_showCalendar", 2);
__decorateClass([
state()
], PdDatepicker.prototype, "_startDateValue", 2);
__decorateClass([
state()
], PdDatepicker.prototype, "_endDateValue", 2);
__decorateClass([
state()
], PdDatepicker.prototype, "_rangeSelection", 2);
__decorateClass([
state()
], PdDatepicker.prototype, "_timeSelection", 2);
PdDatepicker = __decorateClass([
localized()
], PdDatepicker);
export { PdDatepicker };