jb-calendar
Version:
jalali calendar interface web component
932 lines (920 loc) • 33.8 kB
text/typescript
import CSS from "./jb-calendar.css";
import VariableCSS from './variables.css';
import {Direction,InputType,
JBCalendarData,
JBCalendarDateRestrictions,
JBCalendarElements,
JBCalendarSwipeGestureData,
JBCalendarValue,
} from "./types";
import {
getYear,
getMonth,
getDay,
isEqual,
getDaysInMonth,
getDate,
} from "date-fns";
import {
newDate,
isAfter,
isBefore,
getYear as getJalaliYear,
getMonth as getJalaliMonth,
getDay as getJalaliDay,
getDaysInMonth as getJalaliDaysInMonth,
getDate as getJalaliDate,
} from "date-fns-jalali";
import { enToFaDigits } from "jb-core";
import {registerDefaultVariables} from 'jb-core/theme';
import { renderHTML } from "./render";
import { dictionary } from "./i18n";
import { i18n } from "jb-core/i18n";
export * from './types.js';
const InputTypes: { [key: string]: InputType } = {
jalali: "JALALI",
gregorian: "GREGORIAN",
};
export enum JBCalendarSections {
day = "DAY",
month = "MONTH",
year = "YEAR",
}
export type {JBCalendarValue};
const today = new Date();
if (HTMLElement == undefined) {
//in case of server render or old browser
console.error("you cant render web component on a server side");
}
export class JBCalendarWebComponent extends HTMLElement {
#swipeGestureData: JBCalendarSwipeGestureData = {
daysWrapper: {
startX: null,
startY: null,
},
yearsWrapper: {
startX: null,
},
};
#value: JBCalendarValue = {
year: null,
month: null,
day: null,
};
#activeSection: JBCalendarSections | null = null;
#inputType: InputType = i18n.locale.calendar == "persian"?InputTypes.jalali:InputTypes.gregorian
#defaultCalendarData = {
jalali: {
year: getJalaliYear(today),
month: getJalaliMonth(today) + 1,
},
gregorian: {
year: getYear(today),
month: getMonth(today) + 1,
},
};
#jalaliMonthList = dictionary.get(i18n,"jalaliMonthList");
#gregorianMonthList = dictionary.get(i18n,"gregorianMonthList");
/**
* @public change month labels to desired user label base on language or culture
*/
setMonthList(inputType: InputType, monthList: string[]) {
if (Array.isArray(monthList) && monthList.length == 12) {
switch (inputType) {
case "JALALI":
this.#jalaliMonthList = monthList.map((item) => item);
break;
case "GREGORIAN":
this.#gregorianMonthList = monthList;
}
this.#initMonthList();
this.#updateTitleMonth(this.data.selectedMonth);
} else {
console.error("Invalid Month List", monthList);
}
}
get defaultCalendarData() {
return this.#defaultCalendarData;
}
set defaultCalendarData(value) {
this.#defaultCalendarData = value;
}
dateRestrictions: JBCalendarDateRestrictions =
new Proxy<JBCalendarDateRestrictions>(
{
min: null,
max: null,
},
this.createDateRestrictionHandler()
);
data: JBCalendarData = new Proxy(
{
selectedYear: 0,
selectedMonth: 0,
yearSelectionRange: [0, 0],
},
this.createDataHandler()
);
private elements!: JBCalendarElements;
get value() {
return this.#value;
}
set value(value: JBCalendarValue) {
const { year, month, day } = value;
if (year && month && day) {
this.#value.year = year;
this.#value.month = month;
this.#value.day = day;
this.data.selectedYear = year;
this.data.selectedMonth = month;
const prevSelectedDayDom: HTMLDivElement | null =
this.shadowRoot!.querySelector(`.--selected`);
if (prevSelectedDayDom !== null) {
prevSelectedDayDom.classList.remove("--selected");
}
if (this.data.selectedYear == year && this.data.selectedMonth == month) {
const dayDom = this.shadowRoot!.querySelector(
`.day-wrapper[day-number="${day}"]`
);
if (dayDom) {
dayDom.classList.add("--selected");
}
}
} else {
console.error(
"Invalid value. please make sure you have year,month and day"
);
}
}
get activeSection(): JBCalendarSections {
// determine we want to see day picker or month picker ,...
return this.#activeSection || JBCalendarSections.day;
}
set activeSection(value: JBCalendarSections) {
if (value == this.#activeSection) {
return;
}
if (this.#activeSection) {
//if we have active section before
const selectionType :"day"|"month"|"year" = this.#activeSection.toLocaleLowerCase() as any;
this.elements.selectionSections[selectionType].classList.remove("--show");
}
if (value == "DAY") {
this.elements.selectionSections.day.classList.add("--show");
this.elements.navigatorTitle.month.classList.add("--show");
this.elements.navigatorTitle.year.classList.add("--show");
this.elements.navigatorTitle.yearRange.classList.remove("--show");
}
if (value == "MONTH") {
this.elements.selectionSections.month.classList.add("--show");
this.elements.navigatorTitle.month.classList.remove("--show");
this.elements.navigatorTitle.year.classList.add("--show");
this.elements.navigatorTitle.yearRange.classList.remove("--show");
}
if (value == "YEAR") {
this.elements.selectionSections.year.classList.add("--show");
this.elements.navigatorTitle.month.classList.remove("--show");
this.elements.navigatorTitle.year.classList.remove("--show");
this.elements.navigatorTitle.yearRange.classList.add("--show");
}
this.#activeSection = value;
}
get inputType() {
return this.#inputType;
}
set inputType(value) {
this.#inputType = value;
this.onInputTypeChange();
}
#showPersianNumber = i18n.locale.numberingSystem == "arabext";
get showPersianNumber() {
return this.#showPersianNumber;
}
set showPersianNumber(value: boolean) {
if (typeof value !== "boolean") {
console.error("showPersianNumber must be boolean");
return;
}
this.#showPersianNumber = value;
this.setCalendarData();
}
//for layout direction
get cssDirection(): Direction {
return getComputedStyle(this).direction as Direction;
}
#direction: Direction | null = null;
get direction() {
return this.#direction?this.#direction:this.cssDirection;
}
set direction(dir: Direction) {
if (dir && (dir == "ltr" || dir == "rtl")) {
this.#direction = dir;
this.style.direction = dir;
}
this.setupStyleBaseOnCssDirection();
}
constructor() {
super();
this.initWebComponent();
this.initProps();
this.initCalendar();
}
connectedCallback() {
// standard web component event that called when all of dom is bound
this.callOnLoadEvent();
this.setupStyleBaseOnCssDirection();
this.initCalendarLayout();
}
initCalendarLayout() {
this.#initMonthList();
this.fillDayOfWeek();
this.setCalendarData();
}
/**
* @public its public because we cant detect css dir change by js so we have to let user change it manually
* @description set elements direction base on current css direction or seated direction.
* @param dir
*/
setupStyleBaseOnCssDirection(dir: Direction = this.direction) {
//change some calendar style base on css direction of the element
//TODO: use css `if()` when it become standard
if (dir == "ltr") {
this.elements.navigatorTitle.nextButton.classList.add("--css-ltr");
this.elements.navigatorTitle.prevButton.classList.add("--css-ltr");
this.elements.monthDayWrapper.next.classList.add("--css-ltr");
this.elements.monthDayWrapper.prev.classList.add("--css-ltr");
this.elements.yearsWrapper.next.classList.add("--css-ltr");
this.elements.yearsWrapper.prev.classList.add("--css-ltr");
} else if (dir == "rtl") {
this.elements.navigatorTitle.nextButton.classList.remove("--css-ltr");
this.elements.navigatorTitle.prevButton.classList.remove("--css-ltr");
this.elements.monthDayWrapper.next.classList.remove("--css-ltr");
this.elements.monthDayWrapper.prev.classList.remove("--css-ltr");
this.elements.yearsWrapper.next.classList.remove("--css-ltr");
this.elements.yearsWrapper.prev.classList.remove("--css-ltr");
}
}
fillDayOfWeek() {
//fill day of week bas on input type
const gregorianDayOfWeekArray = dictionary.get(i18n,"gregorianDayOfWeek");
const jalaliDayOfWeekArray = dictionary.get(i18n,"jalaliDayOfWeek");
const dayOfWeekArray =
this.inputType == InputTypes.jalali
? jalaliDayOfWeekArray
: gregorianDayOfWeekArray;
this.elements.dayOfWeekWrapper.innerHTML = "";
for (let i = 0; i <= 6; i++) {
//day of week dom
const dowDom = document.createElement("div");
dowDom.classList.add("week-day");
dowDom.innerHTML = dayOfWeekArray[i];
this.elements.dayOfWeekWrapper.appendChild(dowDom);
}
}
setCalendarData() {
// we set default value for selected year and month here because we want user config value and min max date ,... in on init so we update our dom and calendar base on them
if (this.inputType == InputTypes.jalali) {
this.data.selectedYear =
this.value.year || this.data.selectedYear || this.#defaultCalendarData.jalali.year;
this.data.selectedMonth =
this.value.month || this.data.selectedMonth || this.#defaultCalendarData.jalali.month;
this.data.yearSelectionRange = [
this.data.selectedYear - 4,
this.data.selectedYear + 7,
];
//chnage swipe up section text
this.shadowRoot!.querySelector(".swipe-up-text")!.innerHTML =
"نمایش سالها";
} else {
this.data.selectedYear = this.value.year|| this.data.selectedYear || this.#defaultCalendarData.gregorian.year;
this.data.selectedMonth = this.value.month || this.data.selectedMonth || this.#defaultCalendarData.gregorian.month;
this.data.yearSelectionRange = [
this.data.selectedYear - 4,
this.data.selectedYear + 7,
];
//chnage swipe up section text
this.shadowRoot!.querySelector(".swipe-up-text")!.innerHTML =
"show years";
}
}
callOnLoadEvent() {
const event = new CustomEvent("load", { bubbles: true, composed: true });
this.dispatchEvent(event);
}
callOnInitEvent() {
const event = new CustomEvent("init", { bubbles: true, composed: true });
this.dispatchEvent(event);
}
initWebComponent() {
const shadowRoot = this.attachShadow({ mode: "open" });
registerDefaultVariables();
const html = `<style>${CSS} ${VariableCSS}</style>` + "\n" + renderHTML();
const element = document.createElement("template");
element.innerHTML = html;
shadowRoot.appendChild(element.content.cloneNode(true));
this.elements = {
selectionSections: {
day: shadowRoot.querySelector(".day-selection-section")!,
month: shadowRoot.querySelector(".month-selection-section")!,
year: shadowRoot.querySelector(".year-selection-section")!,
},
monthDayWrapper: {
current: shadowRoot.querySelector(".current-month-day-wrapper")!,
prev: shadowRoot.querySelector(".prev-month-day-wrapper")!,
next: shadowRoot.querySelector(".next-month-day-wrapper")!,
},
yearsWrapper: {
current: shadowRoot.querySelector(".current-years-wrapper")!,
prev: shadowRoot.querySelector(".prev-years-wrapper")!,
next: shadowRoot.querySelector(".next-years-wrapper")!,
},
dayOfWeekWrapper: shadowRoot.querySelector(".week-day-wrapper")!,
navigatorTitle: {
month: shadowRoot.querySelector(".navigator-title .month")!,
year: shadowRoot.querySelector(".navigator-title .year")!,
yearRange: shadowRoot.querySelector(".navigator-title .year-range")!,
nextButton: shadowRoot.querySelector(".next-btn")!,
prevButton: shadowRoot.querySelector(".prev-btn")!,
wrapper: shadowRoot.querySelector(".navigator-title")!,
},
swipeupSymbol: shadowRoot.querySelector(".swipe-up-symbol")!,
};
this.registerEventHandlers();
window.matchMedia
}
registerEventHandlers() {
this.elements.navigatorTitle.nextButton.addEventListener("click",this.onNextButtonClicked.bind(this));
this.elements.navigatorTitle.prevButton.addEventListener("click",this.onPrevButtonClicked.bind(this));
this.elements.navigatorTitle.year.addEventListener("click",this.onNavigatorTitleYearClicked.bind(this));
this.elements.navigatorTitle.month.addEventListener("click",this.onNavigatorTitleMonthClicked.bind(this));
//add support for swiping
this.elements.selectionSections.day.addEventListener("touchstart",this.onDayWrapperTouchStart.bind(this)
);
this.elements.selectionSections.day.addEventListener(
"touchmove",
this.onDayWrapperTouchMove.bind(this)
);
this.elements.selectionSections.day.addEventListener(
"touchend",
this.onDayWrapperTouchEnd.bind(this)
);
//swipe for year list
this.elements.selectionSections.year.addEventListener(
"touchstart",
this.onYearWrapperTouchStart.bind(this)
);
this.elements.selectionSections.year.addEventListener(
"touchmove",
this.onYearWrapperTouchMove.bind(this)
);
this.elements.selectionSections.year.addEventListener(
"touchend",
this.onYearWrapperTouchEnd.bind(this)
);
}
onDayWrapperTouchStart(e: TouchEvent) {
this.#swipeGestureData.daysWrapper.startX = e.touches[0].clientX;
this.#swipeGestureData.daysWrapper.startY = e.touches[0].clientY;
}
onDayWrapperTouchMove(e: TouchEvent) {
if (
this.#swipeGestureData.daysWrapper.startX !== null &&
this.#swipeGestureData.daysWrapper.startY !== null
) {
e.preventDefault();
const deltaX =
e.touches[0].clientX - this.#swipeGestureData.daysWrapper.startX;
const deltaY =
e.touches[0].clientY - this.#swipeGestureData.daysWrapper.startY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//when user swipe horizentally
//first wen remove and reset vertical effect
this.elements.swipeupSymbol.classList.remove("--show");
this.elements.swipeupSymbol.style.transform = `translateY(${0}px)`;
this.elements.swipeupSymbol.style.opacity = `0`;
//then move horizentally
this.elements.monthDayWrapper.current.style.transform = `translateX(${deltaX}px)`;
this.elements.monthDayWrapper.prev.style.transform = `translateX(${deltaX}px)`;
this.elements.monthDayWrapper.next.style.transform = `translateX(${deltaX}px)`;
} else {
//if user swipe more vertically than horizontally we reset horizontal swipe change
this.elements.monthDayWrapper.current.style.transform = `translateX(${0}px)`;
this.elements.monthDayWrapper.prev.style.transform = `translateX(${0}px)`;
this.elements.monthDayWrapper.next.style.transform = `translateX(${0}px)`;
//then we move calendar vertically
this.elements.monthDayWrapper.current.style.transform = `translateY(${deltaY}px)`;
if (deltaY < 0) {
//on swipe up
this.elements.swipeupSymbol.classList.add("--show");
const opacity = Math.abs(deltaY) / 70;
if (Math.abs(deltaY) > 32) {
this.elements.swipeupSymbol.style.transform = `translateY(${0}px)`;
} else {
this.elements.swipeupSymbol.style.transform = `translateY(${
deltaY + 32
}px)`;
}
this.elements.swipeupSymbol.style.opacity = `${opacity}`;
}
}
}
}
onDayWrapperTouchEnd(e: TouchEvent) {
//handle horizental swipe
if (this.#swipeGestureData.daysWrapper.startX !== null) {
const clientX = e.changedTouches[0].clientX;
const deltaX = clientX - this.#swipeGestureData.daysWrapper.startX;
this.#swipeGestureData.daysWrapper.startX = null;
if (Math.abs(deltaX) > 100) {
//determine direction of change
let swipeDirection = deltaX > 0 ? "next" : "prev";
if (this.direction == "ltr") {
swipeDirection = deltaX > 0 ? "prev" : "next";
}
//do the transition
if (swipeDirection == "prev") {
this.elements.monthDayWrapper.current.style.transform = `translateX(${0}px)`;
this.elements.monthDayWrapper.prev.style.transform = `translateX(${0}px)`;
this.elements.monthDayWrapper.next.style.transform = `translateX(${0}px)`;
this.onPrevButtonClicked();
} else {
this.elements.monthDayWrapper.current.style.transform = `translateX(${0}px)`;
this.elements.monthDayWrapper.prev.style.transform = `translateX(${0}px)`;
this.elements.monthDayWrapper.next.style.transform = `translateX(${0}px)`;
this.onNextButtonClicked();
}
} else {
this.moveBackToPos(this.elements.monthDayWrapper.current);
this.moveBackToPos(this.elements.monthDayWrapper.prev);
this.moveBackToPos(this.elements.monthDayWrapper.next);
}
}
// handle vertical swipe
if (this.#swipeGestureData.daysWrapper.startY !== null) {
const clientY = e.changedTouches[0].clientY;
const deltaY = clientY - this.#swipeGestureData.daysWrapper.startY;
this.#swipeGestureData.daysWrapper.startY = null;
this.elements.swipeupSymbol.classList.remove("--show");
this.elements.swipeupSymbol.style.transform = `translateY(${0}px)`;
this.elements.swipeupSymbol.style.opacity = `0`;
if (Math.abs(deltaY) > 70) {
if (deltaY > 0) {
//on swipe down
this.activeSection = JBCalendarSections.month;
} else {
this.activeSection = JBCalendarSections.year;
}
}
}
}
onYearWrapperTouchStart(e: TouchEvent) {
this.#swipeGestureData.yearsWrapper.startX = e.touches[0].clientX;
}
onYearWrapperTouchMove(e: TouchEvent) {
if (this.#swipeGestureData.yearsWrapper.startX !== null) {
this.elements.yearsWrapper.current.style.transform = `translateX(${
e.touches[0].clientX - this.#swipeGestureData.yearsWrapper.startX
}px)`;
this.elements.yearsWrapper.prev.style.transform = `translateX(${
e.touches[0].clientX - this.#swipeGestureData.yearsWrapper.startX
}px)`;
this.elements.yearsWrapper.next.style.transform = `translateX(${
e.touches[0].clientX - this.#swipeGestureData.yearsWrapper.startX
}px)`;
}
}
onYearWrapperTouchEnd(e: TouchEvent) {
if (this.#swipeGestureData.yearsWrapper.startX !== null) {
const clientX = e.changedTouches[0].clientX;
const deltaX = clientX - this.#swipeGestureData.yearsWrapper.startX;
this.#swipeGestureData.yearsWrapper.startX = null;
//determine direction of change
let swipeDirection = deltaX > 0 ? "next" : "prev";
if (this.direction == "ltr") {
swipeDirection = deltaX > 0 ? "prev" : "next";
}
if (Math.abs(deltaX) > 100) {
if (swipeDirection == "prev") {
this.elements.yearsWrapper.current.style.transform = `translateX(${0}px)`;
this.elements.yearsWrapper.prev.style.transform = `translateX(${0}px)`;
this.elements.yearsWrapper.next.style.transform = `translateX(${0}px)`;
this.onPrevButtonClicked();
} else {
this.elements.yearsWrapper.current.style.transform = `translateX(${0}px)`;
this.elements.yearsWrapper.prev.style.transform = `translateX(${0}px)`;
this.elements.yearsWrapper.next.style.transform = `translateX(${0}px)`;
this.onNextButtonClicked();
}
} else {
this.moveBackToPos(this.elements.yearsWrapper.current);
this.moveBackToPos(this.elements.yearsWrapper.prev);
this.moveBackToPos(this.elements.yearsWrapper.next);
}
}
}
moveBackToPos(dom: HTMLElement) {
if (dom) {
//remove all transform and changed pos from element and returned it to natural place. used on drop event
dom.style.transition = `transform 0.3s 0s ease`;
//remove above assigned animation
setTimeout(() => {
dom.style.transition = "";
}, 300);
dom.style.transform = ``;
}
}
#updateTitleMonth(monthIndex: number) {
const monthName =
this.inputType == InputTypes.jalali
? this.#jalaliMonthList[monthIndex - 1]
: this.#gregorianMonthList[monthIndex - 1];
this.elements.navigatorTitle.month.innerHTML = monthName;
}
createDataHandler() {
const onYearChanged = (newYear: number, prevYear: number) => {
this.elements.navigatorTitle.year.innerHTML = this.localizeString(
newYear.toString()
);
};
const onMonthChanged = (newMonth: number, prevMonth: number) => {
this.#updateTitleMonth(newMonth);
this.fillMonthDays();
};
const onYearSelectionRangeChanged = (newRange: number[]) => {
this.elements.navigatorTitle.yearRange.innerHTML = this.localizeString(
`${newRange[0]} - ${newRange[1]}`
);
this.fillYearList();
};
const dataHandler = {
set: (obj: any, prop: string, value: number | number[]) => {
if (prop == "selectedYear") {
onYearChanged(value as number, obj.selectedYear);
obj[prop] = value;
}
if (prop == "selectedMonth") {
obj[prop] = value;
onMonthChanged(value as number, obj.selectedMonth);
}
if (prop == "yearSelectionRange" && Array.isArray(value)) {
obj[prop] = value;
onYearSelectionRangeChanged(value);
}
return true;
},
};
return dataHandler;
}
createDateRestrictionHandler() {
const restrictionHandler = {
set: (obj: any, prop: string, value: number) => {
obj[prop] = value;
switch (prop) {
case "min":
if (this.activeSection == "DAY") {
this.fillMonthDays();
}
break;
case "max":
if (this.activeSection == "DAY") {
this.fillMonthDays();
}
break;
}
return true;
},
};
return restrictionHandler;
}
initProps() {
//default input type of this component is jalali
this.callOnInitEvent();
}
selectToday() {
const today = new Date();
if (this.inputType == InputTypes.jalali) {
this.select(
getJalaliYear(today),
getJalaliMonth(today) + 1,
getJalaliDay(today)
);
} else {
this.select(getYear(today), getMonth(today) + 1, getDay(today));
}
}
select(year: number, month: number, day: number) {
this.value = {
year: year,
month: month,
day: day,
};
}
initCalendar() {
if (!this.#activeSection) {
this.activeSection = JBCalendarSections.day;
}
}
#mapGregorianDayOfWeekToJalali(dayNumber: number) {
// for example sunday is 0 so 2(yekshanbe) will return
const mapper = [2, 3, 4, 5, 6, 7, 1];
return mapper[dayNumber];
}
fillYearList() {
this.fillYearListDom(
this.data.yearSelectionRange[0],
this.data.yearSelectionRange[1],
"current"
);
this.fillYearListDom(
this.data.yearSelectionRange[0] - 12,
this.data.yearSelectionRange[1] - 12,
"prev"
);
this.fillYearListDom(
this.data.yearSelectionRange[0] + 12,
this.data.yearSelectionRange[1] + 12,
"next"
);
}
fillYearListDom(startYear: number, endYear: number, type: "current" | "prev" | "next") {
this.elements.yearsWrapper[type].innerHTML = "";
for (let i = startYear; i <= endYear; i++) {
const yearDom = this.createYearDom(i);
this.elements.yearsWrapper[type].appendChild(yearDom);
}
}
createYearDom(year: number) {
const monthDom = document.createElement("div");
monthDom.classList.add("year-wrapper");
const monthTextDom = document.createElement("div");
monthTextDom.classList.add("year-number");
monthTextDom.innerHTML = this.localizeString(year.toString());
monthDom.appendChild(monthTextDom);
monthDom.addEventListener("click", () => {
this.data.selectedYear = year;
this.activeSection = JBCalendarSections.month;
});
return monthDom;
}
#initMonthList() {
this.elements.selectionSections.month.innerHTML = "";
for (let i = 1; i < 13; i++) {
const monthDom = this.#createMonthDom(i);
this.elements.selectionSections.month.appendChild(monthDom);
}
}
#createMonthDom(monthIndex: number) {
const monthDom = document.createElement("div");
monthDom.classList.add("month-wrapper");
const monthTextDom = document.createElement("div");
monthTextDom.classList.add("month-name");
const monthName =
this.inputType == InputTypes.jalali
? this.#jalaliMonthList[monthIndex - 1]
: this.#gregorianMonthList[monthIndex - 1];
monthTextDom.innerHTML = monthName;
monthDom.appendChild(monthTextDom);
monthDom.addEventListener("click", () => {
this.data.selectedMonth = monthIndex;
this.activeSection = JBCalendarSections.day;
});
return monthDom;
}
#getDate(year: number, month: number, day: number): Date {
if (this.inputType == InputTypes.jalali) {
return newDate(year, month - 1, day);
}
return new Date(year, month - 1, day);
}
#getWeekDayIndex(date: Date): number {
if (this.inputType == InputTypes.jalali) {
return this.#mapGregorianDayOfWeekToJalali(getJalaliDay(date));
}
return getDay(date);
}
#getDaysInMonth(date: Date): number {
if (this.inputType == InputTypes.jalali) {
return getJalaliDaysInMonth(date);
}
return getDaysInMonth(date);
}
#isToday(day: number, month: number, year: number): boolean {
const today = new Date();
if (this.inputType == InputTypes.jalali) {
return (
getJalaliYear(today) == year &&
getJalaliMonth(today) == month - 1 &&
getJalaliDate(today) == day
);
}
return (
getYear(today) == year &&
getMonth(today) == month - 1 &&
getDate(today) == day
);
}
fillMonthDaysDom(year: number, month: number, type: "current" | "prev" | "next") {
const firstDayOfMonthDate = this.#getDate(year, month, 1);
// const firstDayInWeek = this.inputType == InputTypes.jalali ? this.mapGregorianDayOfWeekToJalali(firstDayOfMonthDate.day()) : firstDayOfMonthdate.day() + 1;
const firstDayInWeek = this.#getWeekDayIndex(firstDayOfMonthDate);
this.elements.monthDayWrapper[type].innerHTML = "";
for (let i = 1; i < firstDayInWeek; i++) {
const emptyDayDom = this.createEmptyDayDom();
this.elements.monthDayWrapper[type].appendChild(emptyDayDom);
}
const dayInMonth = this.#getDaysInMonth(firstDayOfMonthDate);
for (let i = 1; i <= dayInMonth; i++) {
const dayDate = this.#getDate(
this.data.selectedYear,
this.data.selectedMonth,
i
);
const isToday = this.#isToday(
i,
this.data.selectedMonth,
this.data.selectedYear
);
const isSelected =
this.value.year == this.data.selectedYear &&
this.value.month == this.data.selectedMonth &&
this.value.day == i;
const isDisable = !this.checkIsDayDisable(dayDate).isAllValid;
const dayDom = this.createDayDom(
i,
this.data.selectedYear,
this.data.selectedMonth,
isToday,
isSelected,
isDisable
);
this.elements.monthDayWrapper[type].appendChild(dayDom);
}
}
fillMonthDays() {
const currentMonth = this.data.selectedMonth;
const currentYear = this.data.selectedYear;
//set prev month days
let prevMonth, prevYear;
if (currentMonth > 1) {
prevMonth = currentMonth - 1;
prevYear = this.data.selectedYear;
} else {
prevYear = this.data.selectedYear - 1;
prevMonth = 12;
}
this.fillMonthDaysDom(prevYear, prevMonth, "prev");
//update current month days
this.fillMonthDaysDom(currentYear, currentMonth, "current");
//update next month days
let nextMonth: number, nextYear: number;
if (currentMonth < 12) {
nextMonth = currentMonth + 1;
nextYear = this.data.selectedYear;
} else {
nextYear = this.data.selectedYear + 1;
nextMonth = 1;
}
this.fillMonthDaysDom(nextYear, nextMonth, "next");
}
checkIsDayDisable(dayDate: Date) {
const result = {
min: true,
max: true,
get isAllValid() {
return this.min && this.max;
},
};
if (this.dateRestrictions.min) {
result.min =
isAfter(dayDate, this.dateRestrictions.min) ||
isEqual(dayDate, this.dateRestrictions.min);
}
if (this.dateRestrictions.max) {
result.max =
isBefore(dayDate, this.dateRestrictions.max) ||
isEqual(dayDate, this.dateRestrictions.max);
}
return result;
}
createEmptyDayDom() {
const dayDom = document.createElement("div");
dayDom.classList.add("empty-day");
return dayDom;
}
createDayDom(
dayNumber: number,
year: number,
month: number,
isToday: boolean,
isSelected: boolean,
isDisable: boolean
) {
//create dom
const dayDom = document.createElement("div");
dayDom.classList.add("day-wrapper");
dayDom.setAttribute("day-number", dayNumber.toString());
if (isToday) {
dayDom.classList.add("--today");
}
if (isSelected) {
dayDom.classList.add("--selected");
}
//
const dayNumberWrapperDom = document.createElement("div");
dayNumberWrapperDom.classList.add("day-number-wrapper");
//
const dayNumberDom = document.createElement("div");
dayNumberDom.classList.add("day-number");
dayNumberDom.innerHTML = this.localizeString(dayNumber.toString());
const statusPoint = document.createElement("div");
statusPoint.classList.add("status-point");
//
dayNumberWrapperDom.appendChild(dayNumberDom);
dayDom.appendChild(statusPoint);
dayDom.appendChild(dayNumberWrapperDom);
if (!isDisable) {
// add event listeners
dayDom.addEventListener("click", () => {
this.onDayClicked(year, month, dayNumber);
});
} else {
dayDom.classList.add("--disable");
}
return dayDom;
}
onDayClicked(year: number, month: number, dayNumber: number) {
this.select(year, month, dayNumber);
const event = new CustomEvent("select");
this.dispatchEvent(event);
}
onNextButtonClicked() {
if (this.activeSection == "DAY") {
const selectedMonth = this.data.selectedMonth;
if (selectedMonth < 12) {
this.data.selectedMonth = selectedMonth + 1;
} else {
this.data.selectedYear = this.data.selectedYear + 1;
this.data.selectedMonth = 1;
}
}
if (this.activeSection == "MONTH") {
this.data.selectedYear = this.data.selectedYear + 1;
}
if (this.activeSection == "YEAR") {
const minYear = this.data.yearSelectionRange[0] + 12;
const maxYear = this.data.yearSelectionRange[1] + 12;
this.data.yearSelectionRange = [minYear, maxYear];
}
}
onPrevButtonClicked() {
if (this.activeSection == "DAY") {
const selectedMonth = this.data.selectedMonth;
if (selectedMonth > 1) {
this.data.selectedMonth = selectedMonth - 1;
} else {
this.data.selectedYear = this.data.selectedYear - 1;
this.data.selectedMonth = 12;
}
}
if (this.activeSection == "MONTH") {
this.data.selectedYear = this.data.selectedYear - 1;
}
if (this.activeSection == "YEAR") {
const minYear = this.data.yearSelectionRange[0] - 12;
const maxYear = this.data.yearSelectionRange[1] - 12;
if (minYear > 0) {
this.data.yearSelectionRange = [minYear, maxYear];
}
}
}
onNavigatorTitleYearClicked() {
if (
this.activeSection == JBCalendarSections.day ||
this.activeSection == JBCalendarSections.month
) {
this.activeSection = JBCalendarSections.year;
}
}
onNavigatorTitleMonthClicked() {
if (this.activeSection == JBCalendarSections.day) {
this.activeSection = JBCalendarSections.month;
}
}
onInputTypeChange() {
// when date input type change this function get called
this.setCalendarData();
this.fillDayOfWeek();
this.#initMonthList();
}
localizeString(string: string): string {
if (this.showPersianNumber) {
return enToFaDigits(string);
} else {
return string;
}
}
}
const myElementNotExists = !customElements.get("jb-calendar");
if (myElementNotExists) {
window.customElements.define("jb-calendar", JBCalendarWebComponent);
}