@webwriter/timeline
Version:
Create/learn with a digital timeline and test your knowledge.
385 lines (358 loc) • 11.4 kB
text/typescript
import { LitElement, html, css } from "lit";
import { customElement, property, query, queryAll } from "lit/decorators.js";
import IconCalendarMonth from "@tabler/icons/outline/calendar-month.svg";
import "@shoelace-style/shoelace/dist/themes/light.css";
import { SlInput } from "@shoelace-style/shoelace";
import { SlIcon } from "@shoelace-style/shoelace";
("dialog-date-picker")
export class DialogDatePicker extends LitElement {
({ type: String }) day = "";
({ type: String }) month = "";
({ type: String }) year = "";
({ type: String }) date = "";
({ type: String }) label = "";
({ type: Boolean }) accessor useEndDate;
({ type: Boolean }) accessor useTimePeriod = false;
({ type: Boolean }) accessor invalid = false;
("sl-input") accessor dates;
("#day") accessor dayInput: SlInput;
("#month") accessor monthInput: SlInput;
("#year") accessor yearInput: SlInput;
static styles = css`
.date-div-disabled {
background: #f7f7f8;
}
.date-div,
.date-div-disabled {
display: flex;
flex-direction: row;
width: 89%;
min-width: 245px;
}
.date-container,
.date-container-disabled {
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
border: 1px solid #d6d6da;
border-radius: 5px;
background: white;
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.date-container-disabled {
background: #f7f7f8;
}
.divider {
color: lightgray;
width: 10px;
align-self: center;
}
label {
font-size: 1rem;
font-weight: 400;
margin-bottom: 0.5rem;
}
.date {
--sl-input-border-color: transparent;
--sl-input-border-width: 0;
--sl-input-padding-vertical: 0;
--sl-input-padding-horizontal: 1rem;
text-align: center;
min-width: 3%;
}
.date[disabled] {
--sl-input-color: gray;
}
.date[invalid] {
--sl-input-border-color: var(--sl-color-danger-600);
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300);
outline: none;
border-radius: 3px;
}
`;
static get scopedElements() {
return {
"sl-icon": SlIcon,
"sl-input": SlInput,
};
}
render() {
return html`
<div>
<label>${this.label}</label> <br />
<div
class="${!this.useTimePeriod && this.useEndDate
? "date-container-disabled"
: "date-container"}"
>
<sl-icon-button src=${IconCalendarMonth} class="calender-icon">
</sl-icon-button>
<div
class="${!this.useTimePeriod && this.useEndDate
? "date-div-disabled"
: "date-div"}"
>
<!-- day -->
<sl-input
class="date"
type="text"
id="day"
.value="${this.day}"
@sl-input="${(e: Event) => {
const input = e.target as SlInput;
this.day =
input.value.length > 0 ? input.value.padStart(2, "0") : "";
}}"
@keypress="${this.validateInput}"
placeholder="DD"
?disabled="${!this.useTimePeriod && this.useEndDate}"
maxlength="2"
valueAsString
></sl-input>
<span class="divider">/</span>
<!-- month -->
<sl-input
class="date"
type="text"
id="month"
.value="${this.month}"
@sl-input="${(e: Event) => {
const input = e.target as SlInput;
this.month =
input.value.length > 0 ? input.value.padStart(2, "0") : "";
}}"
@keypress="${this.validateInput}"
placeholder="MM"
?disabled="${!this.useTimePeriod && this.useEndDate}"
maxlength="2"
valueAsString
></sl-input>
<span class="divider">/</span>
<!-- year -->
<sl-input
class="date"
type="text"
id="year"
.value="${this.year}"
@sl-input="${(e: Event) => {
const input = e.target as SlInput;
this.year = input.value;
}}"
@keypress="${this.validateYearInput}"
@sl-change="${this.validateForErrors}"
placeholder="* YYYY"
?disabled="${!this.useTimePeriod && this.useEndDate}"
maxlength="5"
valueAsString
required
></sl-input>
</div>
</div>
</div>
`;
}
// only numbers are allowed for input
validateInput(e: KeyboardEvent) {
if (!/[0-9]/.test(e.key)) {
e.preventDefault();
}
}
// for year input "-"" sign and numbers are allowed and string length is adjusted
validateYearInput(e: KeyboardEvent) {
if (
!/[0-9]/.test(e.key) &&
!(e.key === "-" && (e.target as SlInput).value === "")
) {
e.preventDefault();
}
}
// day is number 01-31, based on month day is restricted
validateDay() {
const day = parseInt(this.day);
const month = parseInt(this.month);
const year = parseInt(this.year);
if (day < 1) {
return { valid: false, errorMessage: "Days start at least by 1" };
}
if (day > 31) {
return {
valid: false,
errorMessage: "There is no month with more than 31 days",
};
}
if (month == 2) {
const isLeapYear =
(year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
const maxDays = isLeapYear ? 29 : 28;
if (day > maxDays) {
if (this.year) {
return {
valid: false,
errorMessage: `February ${year} has ${maxDays} days (${
isLeapYear ? "leap year" : "not a leap year"
})`,
};
} else {
return {
valid: false,
errorMessage: `February has 29 days or less`,
};
}
}
} else if ([4, 6, 9, 11].includes(month)) {
if (day > 30) {
return { valid: false, errorMessage: "This month has only 30 days" };
}
}
return { valid: true, errorMessage: "" };
}
// month is number 01-12
validateMonth() {
const month = parseInt(this.month);
if (month < 1 || month > 12) {
return {
valid: false,
errorMessage: "There is no year with more than 12 months",
};
}
return { valid: true, errorMessage: "" };
}
// year is number with 4 digits and if "-" its 5 (todo: adjust maxlength in html)
validateYear() {
if ( this.year.length === 0 || (this.year.startsWith("-") && this.year.length === 1) ) {
return { valid: false, errorMessage: "Please enter a year" };
} else if (this.year.length === 5 && !this.year.startsWith("-")) {
return {
valid: false,
errorMessage: "Please enter a year with maximum 4 digits",
};
}
return { valid: true, errorMessage: "" };
}
validateFormat() {
if (this.day && !this.month && this.year) {
return { valid: false, errorMessage: "Please enter a year" };
}
return { valid: true, errorMessage: "" };
}
// validate day and month input, TO DO: fix css for input fields when invalid
validateForErrors() {
const dayValidation = this.validateDay();
const monthValidation = this.validateMonth();
const yearValidation = this.validateYear();
const formatValidation = this.validateFormat();
if (this.day.length >= 1) {
this.monthInput.setAttribute("placeholder", "* MM");
} else {
this.monthInput.setAttribute("placeholder", "MM");
}
// invalid day, dispatch error message to dialog
if (this.day.length > 0 && !dayValidation.valid) {
this.dayInput.setAttribute("invalid", "true");
this.dispatchEvent(
new CustomEvent("show-day-validation-error", {
detail: { errorMessage: dayValidation.errorMessage },
bubbles: true,
composed: true,
})
);
} else {
if (this.dayInput.hasAttribute("invalid")) {
this.dayInput.removeAttribute("invalid");
}
this.dispatchEvent(
new CustomEvent("hide-day-validation-error", {
bubbles: true,
composed: true,
})
);
}
// invalid month, dispatch error message to dialog
if (this.month.length > 0 && !monthValidation.valid) {
this.monthInput.setAttribute("invalid", "true");
this.dispatchEvent(
new CustomEvent("show-month-validation-error", {
detail: { errorMessage: monthValidation.errorMessage },
bubbles: true,
composed: true,
})
);
} else {
if (this.monthInput.hasAttribute("invalid")) {
this.monthInput.removeAttribute("invalid");
}
this.dispatchEvent(
new CustomEvent("hide-month-validation-error", {
bubbles: true,
composed: true,
})
);
}
// invalid year, dispatch error message to dialog
if (!yearValidation.valid) {
if(yearValidation.errorMessage==="Please enter a year" && this.day.length > 0 && this.month.length > 0){
setTimeout(() => {
this.yearInput.setAttribute("invalid", "true");
}, 4500);
} else if(yearValidation.errorMessage==="Please enter a year with maximum 4 digits") {
this.yearInput.setAttribute("invalid", "true");
}
this.dispatchEvent(
new CustomEvent("show-year-validation-error", {
detail: { errorMessage: yearValidation.errorMessage },
bubbles: true,
composed: true,
})
);
} else {
this.yearInput.removeAttribute("invalid");
this.dispatchEvent(
new CustomEvent("hide-year-validation-error", {
bubbles: true,
composed: true,
})
);
}
// invalid format, dispatch error message to dialog
if (!formatValidation.valid) {
this.dispatchEvent(
new CustomEvent("show-format-validation-error", {
detail: {
errorMessage: "Error: Invalid format (dd/yyyy), enter a month.",
},
bubbles: true,
composed: true,
})
);
this.monthInput.setAttribute("invalid", "true");
} else {
this.monthInput.removeAttribute("invalid");
this.dispatchEvent(
new CustomEvent("hide-format-validation-error", {
bubbles: true,
composed: true,
})
);
}
}
// reset all values, reset method used in dialog
reset() {
this.day = this.month = this.year = this.date = "";
if (this.dayInput.hasAttribute("invalid")) {
this.dayInput.removeAttribute("invalid");
}
if (this.monthInput.hasAttribute("invalid")) {
this.monthInput.removeAttribute("invalid");
}
if (this.yearInput.hasAttribute("invalid")) {
this.yearInput.removeAttribute("invalid");
}
this.monthInput.setAttribute("placeholder", "MM");
this.dates?.forEach((input: SlInput) => {
input.value = "";
});
}
}