flatpickr
Version:
A lightweight, powerful javascript datetime picker
1,410 lines (1,084 loc) • 35.4 kB
JavaScript
const flatpickr = function (selector, config) {
let elements;
const createInstance = function (element) {
if (element._flatpickr) {
element._flatpickr.destroy();
}
element._flatpickr = new flatpickr.init(element, config);
return element._flatpickr;
};
if (selector.nodeName) {
return createInstance(selector);
}
/*
Utilize the performance of native getters if applicable
https://jsperf.com/getelementsbyclassname-vs-queryselectorall/18
https://jsperf.com/jquery-vs-javascript-performance-comparison/22
*/
else if (/^#[a-zA-Z0-9\-_]*$/.test(selector)) {
return createInstance(document.getElementById(selector.slice(1)));
}
else if (/^\.[a-zA-Z0-9\-_]*$/.test(selector)) {
elements = document.getElementsByClassName(selector.slice(1));
}
else {
elements = document.querySelectorAll(selector);
}
let instances = [];
for (let i = 0; i < elements.length; i++) {
instances.push(createInstance(elements[i]));
}
if (instances.length === 1) {
return instances[0];
}
return {
calendars: instances,
byID: id => document.getElementById(id)._flatpickr
};
};
/**
* @constructor
*/
flatpickr.init = function (element, instanceConfig) {
function createElement(tag, className, content) {
let newElement = document.createElement(tag);
if (content) {
newElement.textContent = content;
}
if (className) {
newElement.className = className;
}
return newElement;
}
const debounce = (func, wait, immediate) => {
let timeout;
return function (...args) {
const context = this;
const later = function () {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (immediate && !timeout) {
func.apply(context, args);
}
};
};
// functions
const self = this;
let parseConfig,
init,
wrap,
uDate,
equalDates,
pad,
monthToStr,
isEnabled,
buildMonthNavigation,
buildWeekdays,
buildCalendar,
buildDays,
buildWeeks,
buildTime,
timeWrapper,
yearScroll,
updateValue,
amPMToggle,
onKeyDown,
onResize,
updateNavigationCurrentMonth,
handleYearChange,
changeMonth,
getDaysinMonth,
documentClick,
selectDate,
getRandomCalendarIdStr,
bind,
triggerChange;
// elements & variables
let calendarContainer,
weekdayContainer,
timeContainer,
navigationCurrentMonth,
monthsNav,
prevMonthNav,
currentYearElement,
currentMonthElement,
nextMonthNav,
calendar,
weekNumbers,
now = new Date(),
wrapperElement,
clickEvt;
self.formats = {
// weekday name, short, e.g. Thu
D: () => self.l10n.weekdays.shorthand[self.formats.w()],
// full month name e.g. January
F: () => monthToStr(self.formats.n() - 1, false),
// hours with leading zero e.g. 03
H: () => pad(self.selectedDateObj.getHours()),
// day (1-30) with ordinal suffix e.g. 1st, 2nd
J: () => self.formats.j() + self.l10n.ordinal(self.formats.j()),
// AM/PM
K: () => self.selectedDateObj.getHours() > 11 ? "PM" : "AM",
// shorthand month e.g. Jan, Sep, Oct, etc
M: () => monthToStr(self.formats.n() - 1, true),
// seconds 00-59
S: () => pad(self.selectedDateObj.getSeconds()),
// unix timestamp
U: () => self.selectedDateObj.getTime() / 1000,
// full year e.g. 2016
Y: () => self.selectedDateObj.getFullYear(),
// day in month, padded (01-30)
d: () => pad(self.formats.j()),
// hour from 1-12 (am/pm)
h: () => self.selectedDateObj.getHours() % 12 ? self.selectedDateObj.getHours() % 12 : 12,
// minutes, padded with leading zero e.g. 09
i: () => pad(self.selectedDateObj.getMinutes()),
// day in month (1-30)
j: () => self.selectedDateObj.getDate(),
// weekday name, full, e.g. Thursday
l: () => self.l10n.weekdays.longhand[self.formats.w()],
// padded month number (01-12)
m: () => pad(self.formats.n()),
// the month number (1-12)
n: () => self.selectedDateObj.getMonth() + 1,
// seconds 0-59
s: () => self.selectedDateObj.getSeconds(),
// number of the day of the week
w: () => self.selectedDateObj.getDay(),
// last two digits of year e.g. 16 for 2016
y: () => String(self.formats.Y()).substring(2)
};
self.defaultConfig = {
/* if true, dates will be parsed, formatted, and displayed in UTC.
preloading date strings w/ timezones is recommended but not necessary */
utc: false,
// wrap: see https://chmln.github.io/flatpickr/#strap
wrap: false,
// enables week numbers
weekNumbers: false,
allowInput: false,
/*
clicking on input opens the date(time)picker.
disable if you wish to open the calendar manually with .open()
*/
clickOpens: true,
// display time picker in 24 hour mode
time_24hr: false,
// enables the time picker functionality
enableTime: false,
// noCalendar: true will hide the calendar. use for a time picker along w/ enableTime
noCalendar: false,
// more date format chars at https://chmln.github.io/flatpickr/#dateformat
dateFormat: "Y-m-d",
// altInput - see https://chmln.github.io/flatpickr/#altinput
altInput: false,
// the created altInput element will have this class.
altInputClass: "",
// same as dateFormat, but for altInput
altFormat: "F j, Y", // defaults to e.g. June 10, 2016
// defaultDate - either a datestring or a date object. used for datetimepicker"s initial value
defaultDate: null,
// the minimum date that user can pick (inclusive)
minDate: null,
// the maximum date that user can pick (inclusive)
maxDate: null,
// dateparser that transforms a given string to a date object
parseDate: null,
// see https://chmln.github.io/flatpickr/#disable
enable: [],
// see https://chmln.github.io/flatpickr/#disable
disable: [],
// display the short version of month names - e.g. Sep instead of September
shorthandCurrentMonth: false,
// displays calendar inline. see https://chmln.github.io/flatpickr/#inline-calendar
inline: false,
// position calendar inside wrapper and next to the input element
// leave at false unless you know what you"re doing
static: false,
// code for previous/next icons. this is where you put your custom icon code e.g. fontawesome
prevArrow: "<",
nextArrow: ">",
// enables seconds in the time picker
enableSeconds: false,
// step size used when scrolling/incrementing the hour element
hourIncrement: 1,
// step size used when scrolling/incrementing the minute element
minuteIncrement: 5,
// onChange callback when user selects a date or time
onChange: null, // function (dateObj, dateStr) {}
// called every time calendar is opened
onOpen: null, // function (dateObj, dateStr) {}
// called every time calendar is closed
onClose: null, // function (dateObj, dateStr) {}
onValueUpdate: null
};
init = function () {
instanceConfig = instanceConfig || {};
self.element = element;
parseConfig();
self.input = self.config.wrap ? element.querySelector("[data-input]") : element;
self.input.classList.add("flatpickr-input");
if (self.config.defaultDate) {
self.config.defaultDate = uDate(self.config.defaultDate);
}
if (self.input.value || self.config.defaultDate) {
self.selectedDateObj = uDate(self.config.defaultDate || self.input.value);
}
wrap();
buildCalendar();
bind();
self.uDate = uDate;
self.jumpToDate();
updateValue();
};
parseConfig = function () {
self.config = {};
Object.keys(self.defaultConfig).forEach(key => {
if (instanceConfig.hasOwnProperty(key)) {
self.config[key] = instanceConfig[key];
}
else if (self.element.dataset && self.element.dataset.hasOwnProperty(key.toLowerCase())) {
self.config[key] = self.element.dataset[key.toLowerCase()];
}
else if (!self.element.dataset && self.element.hasAttribute("data-" + key)) {
self.config[key] = self.element.getAttribute("data-" + key);
}
else {
self.config[key] = flatpickr.init.prototype.defaultConfig[key] || self.defaultConfig[key];
}
if (typeof self.defaultConfig[key] === "boolean") {
self.config[key] = self.config[key] === true ||
self.config[key] === "" ||
self.config[key] === "true";
}
if (key === "enableTime" && self.config[key]) {
self.defaultConfig.dateFormat = (!self.config.time_24hr ? "Y-m-d h:i K" : "Y-m-d H:i");
self.defaultConfig.altFormat = (!self.config.time_24hr ? "F j Y, h:i K" : "F j, Y H:i");
}
else if (key === "noCalendar" && self.config[key]) {
self.defaultConfig.dateFormat = "h:i K";
self.defaultConfig.altFormat = "h:i K";
}
});
};
getRandomCalendarIdStr = function () {
let randNum,
idStr;
do {
randNum = Math.round(Math.random() * Math.pow(10, 10));
idStr = `flatpickr-${randNum}`;
} while (document.getElementById(idStr) !== null);
return idStr;
};
uDate = function (date, timeless) {
timeless = timeless || false;
if (date === "today") {
date = new Date();
timeless = true;
}
else if (typeof date === "string") {
date = date.trim();
if (self.config.parseDate) {
date = self.config.parseDate(date);
}
else if (/^\d\d\d\d\-\d{1,2}\-\d\d$/.test(date)) {
// this utc datestring gets parsed, but incorrectly by Date.parse
date = new Date(date.replace(/(\d)-(\d)/g, "$1/$2"));
}
else if (Date.parse(date)) {
date = new Date(date);
}
else if (/^\d\d\d\d\-\d\d\-\d\d/.test(date)) { // disable special utc datestring
date = new Date(date.replace(/(\d)-(\d)/g, "$1/$2"));
}
else if (/^(\d?\d):(\d\d)/.test(date)) { // time-only picker
const matches = date.match(/^(\d?\d):(\d\d)(:(\d\d))?/),
seconds = matches[4] !== undefined ? matches[4] : 0;
date = new Date();
date.setHours(matches[1], matches[2], seconds, 0);
}
else {
console.error(`flatpickr: invalid date string ${date}`);
console.info(self.element);
}
}
if (!(date instanceof Date) || !date.getTime()) {
return null;
}
if (self.config.utc && !date.fp_isUTC) {
date = date.fp_toUTC();
}
if (timeless) {
date.setHours(0, 0, 0, 0);
}
return date;
};
equalDates = (date1, date2) => (
date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getFullYear() === date2.getFullYear()
);
wrap = function () {
wrapperElement = createElement("div", "flatpickr-wrapper");
if (self.config.inline || self.config.static) {
// Wrap input and place calendar underneath
self.element.parentNode.insertBefore(wrapperElement, self.element);
wrapperElement.appendChild(self.element);
wrapperElement.classList.add(self.config.inline ? "inline" : "static");
}
else {
// Insert at bottom of BODY tag to display outside
// of relative positioned elements with css "overflow: hidden;"
// property set.
document.body.appendChild(wrapperElement);
}
if (self.config.altInput) {
// replicate self.element
self.altInput = createElement(
self.input.nodeName,
self.config.altInputClass + " flatpickr-input"
);
self.altInput.placeholder = self.input.placeholder;
self.altInput.type = "text";
self.input.type = "hidden";
self.input.parentNode.insertBefore(self.altInput, self.input.nextSibling);
}
};
getDaysinMonth = function (month = self.currentMonth) {
const yr = self.currentYear;
if (month === 1 && (((yr % 4 === 0) && (yr % 100 !== 0)) || (yr % 400 === 0))) {
return 29;
}
return self.l10n.daysInMonth[month];
};
updateValue = function (e) {
if (self.config.noCalendar && !self.selectedDateObj) {
// picking time only and method triggered from picker
self.selectedDateObj = new Date();
}
else if (!self.selectedDateObj) {
return;
}
if (e) {
e.target.blur();
}
let timeHasChanged;
if (self.config.enableTime) {
const previousTimestamp = self.selectedDateObj.getTime();
// update time
let hours = (parseInt(self.hourElement.value, 10) || 0),
seconds;
const minutes = (60 + (parseInt(self.minuteElement.value, 10) || 0)) % 60;
if (self.config.enableSeconds) {
seconds = (60 + (parseInt(self.secondElement.value, 10)) || 0) % 60;
}
if (!self.config.time_24hr) {
// the real number of hours for the date object
hours = hours % 12 + 12 * (self.amPM.innerHTML === "PM");
}
self.selectedDateObj.setHours(
hours,
minutes,
seconds === undefined ? self.selectedDateObj.getSeconds() : seconds
);
self.hourElement.value = pad(
!self.config.time_24hr ? (12 + hours) % 12 + 12 * (hours % 12 === 0) : hours
);
self.minuteElement.value = pad(minutes);
if (seconds !== undefined) {
self.secondElement.value = pad(seconds);
}
timeHasChanged = self.selectedDateObj.getTime() !== previousTimestamp;
}
self.input.value = self.formatDate(self.config.dateFormat);
if (self.altInput) {
self.altInput.value = self.formatDate(self.config.altFormat);
}
if (e && (timeHasChanged || e.target.classList.contains("flatpickr-day"))) {
triggerChange();
}
if (self.config.onValueUpdate) {
self.config.onValueUpdate(self.selectedDateObj, self.input.value);
}
};
pad = num => `0${num}`.slice(-2);
self.formatDate = function (dateFormat) {
let formattedDate = "";
const formatPieces = dateFormat.split("");
for (let i = 0; i < formatPieces.length; i++) {
const c = formatPieces[i];
if (self.formats.hasOwnProperty(c) && formatPieces[i - 1] !== "\\") {
formattedDate += self.formats[c]();
}
else if (c !== "\\") {
formattedDate += c;
}
}
return formattedDate;
};
monthToStr = function (date, shorthand) {
if (shorthand || self.config.shorthandCurrentMonth) {
return self.l10n.months.shorthand[date];
}
return self.l10n.months.longhand[date];
};
isEnabled = function (dateToCheck) {
if (
(self.config.minDate && dateToCheck < self.config.minDate) ||
(self.config.maxDate && dateToCheck > self.config.maxDate)
) {
return false;
}
dateToCheck = uDate(dateToCheck, true); // timeless
const bool = self.config.enable.length > 0,
array = bool ? self.config.enable : self.config.disable;
let d;
for (let i = 0; i < array.length; i++) {
d = array[i];
if (d instanceof Function && d(dateToCheck)) { // disabled by function
return bool;
}
else if ( // disabled weekday
typeof d === "string" &&
/^wkd/.test(d) &&
dateToCheck.getDay() === (parseInt(d.slice(-1), 10) + self.l10n.firstDayOfWeek - 1) % 7
) {
return bool;
}
else if ((d instanceof Date || (typeof d === "string" && !/^wkd/.test(d))) &&
uDate(d, true).getTime() === dateToCheck.getTime()
) {
// disabled by date string
return bool;
}
else if ( // disabled by range
typeof d === "object" &&
d.hasOwnProperty("from") &&
dateToCheck >= uDate(d.from) &&
dateToCheck <= uDate(d.to)
) {
return bool;
}
}
return !bool;
};
yearScroll = event => {
event.preventDefault();
let delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.deltaY)));
self.currentYear = event.target.value = parseInt(event.target.value, 10) + delta;
self.redraw();
};
timeWrapper = function (e) {
e.preventDefault();
const min = parseInt(e.target.min, 10),
max = parseInt(e.target.max, 10),
step = parseInt(e.target.step, 10),
value = parseInt(e.target.value, 10);
let newValue = value;
if (e.type === "wheel") {
newValue = value + step * (Math.max(-1, Math.min(1, (e.wheelDelta || -e.deltaY))));
}
if (newValue <= min) {
newValue = max - step;
}
else if (newValue >= max) {
newValue = min + step;
}
e.target.value = pad(newValue);
};
updateNavigationCurrentMonth = function () {
currentMonthElement.textContent = monthToStr(self.currentMonth) + " ";
currentYearElement.value = self.currentYear;
};
handleYearChange = function () {
if (self.currentMonth < 0 || self.currentMonth > 11) {
self.currentYear += self.currentMonth % 11;
self.currentMonth = (self.currentMonth + 12) % 12;
}
};
documentClick = function (e) {
const isCalendarElement = wrapperElement.contains(e.target),
isInput = self.element.contains(e.target) || e.target === self.altInput;
if (self.isOpen && !isCalendarElement && !isInput) {
self.close();
}
};
changeMonth = function (offset) {
self.currentMonth += offset;
handleYearChange();
updateNavigationCurrentMonth();
buildDays();
(self.config.noCalendar ? timeContainer : calendar).focus();
};
selectDate = function (e) {
e.preventDefault();
e.stopPropagation();
if (self.config.allowInput && e.target === (self.altInput || self.input) && e.which === 13) {
self.setDate((self.altInput || self.input).value);
self.redraw();
}
else if (e.target.classList.contains("flatpickr-day")) {
const isPrevMonthDay = e.target.classList.contains("prevMonthDay"),
isNextMonthDay = e.target.classList.contains("nextMonthDay"),
monthNum = self.currentMonth - isPrevMonthDay + isNextMonthDay;
if (isPrevMonthDay || isNextMonthDay) {
changeMonth(+isNextMonthDay - isPrevMonthDay);
}
self.selectedDateObj = new Date(self.currentYear, monthNum, e.target.innerHTML);
updateValue(e);
buildDays();
}
};
buildCalendar = function () {
calendarContainer = createElement("div", "flatpickr-calendar");
calendarContainer.id = getRandomCalendarIdStr();
calendar = createElement("div", "flatpickr-days");
calendar.tabIndex = -1;
if (!self.config.noCalendar) {
buildMonthNavigation();
buildWeekdays();
if (self.config.weekNumbers) {
buildWeeks();
}
buildDays();
calendarContainer.appendChild(calendar);
}
wrapperElement.appendChild(calendarContainer);
if (self.config.enableTime) {
buildTime();
}
};
buildMonthNavigation = function () {
monthsNav = createElement("div", "flatpickr-month");
prevMonthNav = createElement("span", "flatpickr-prev-month");
prevMonthNav.innerHTML = self.config.prevArrow;
currentMonthElement = createElement("span", "cur_month");
currentYearElement = createElement("input", "cur_year");
currentYearElement.type = "number";
currentYearElement.title = self.l10n.scrollTitle;
nextMonthNav = createElement("span", "flatpickr-next-month");
nextMonthNav.innerHTML = self.config.nextArrow;
navigationCurrentMonth = createElement("span", "flatpickr-current-month");
navigationCurrentMonth.appendChild(currentMonthElement);
navigationCurrentMonth.appendChild(currentYearElement);
monthsNav.appendChild(prevMonthNav);
monthsNav.appendChild(navigationCurrentMonth);
monthsNav.appendChild(nextMonthNav);
calendarContainer.appendChild(monthsNav);
updateNavigationCurrentMonth();
};
buildWeekdays = function () {
weekdayContainer = createElement("div", "flatpickr-weekdays");
const firstDayOfWeek = self.l10n.firstDayOfWeek;
let weekdays = self.l10n.weekdays.shorthand.slice();
if (firstDayOfWeek > 0 && firstDayOfWeek < weekdays.length) {
weekdays = [].concat(
weekdays.splice(firstDayOfWeek, weekdays.length),
weekdays.splice(0, firstDayOfWeek)
);
}
if (self.config.weekNumbers) {
weekdayContainer.innerHTML = `<span>${self.l10n.weekAbbreviation}</span>`;
}
weekdayContainer.innerHTML += `<span>${weekdays.join("</span><span>")}</span>`;
calendarContainer.appendChild(weekdayContainer);
};
buildWeeks = function () {
calendarContainer.classList.add("hasWeeks");
weekNumbers = createElement("div", "flatpickr-weeks");
calendarContainer.appendChild(weekNumbers);
};
buildDays = function () {
const firstOfMonth = (
new Date(self.currentYear, self.currentMonth, 1).getDay() -
self.l10n.firstDayOfWeek + 7
) % 7,
daysInMonth = getDaysinMonth(),
prevMonthDays = getDaysinMonth((self.currentMonth - 1 + 12) % 12),
days = document.createDocumentFragment();
let dayNumber = prevMonthDays + 1 - firstOfMonth,
currentDate,
dateIsDisabled;
if (self.config.weekNumbers) {
weekNumbers.innerHTML = "";
}
calendar.innerHTML = "";
self.config.minDate = uDate(self.config.minDate, true);
self.config.maxDate = uDate(self.config.maxDate, true);
// prepend days from the ending of previous month
for (; dayNumber <= prevMonthDays; dayNumber++) {
const curDate = new Date(self.currentYear, self.currentMonth - 1, dayNumber, 0, 0, 0, 0, 0),
dateIsEnabled = isEnabled(curDate),
dayElem = createElement(
"span",
dateIsEnabled ? "flatpickr-day prevMonthDay" : "disabled",
dayNumber
);
if (dateIsEnabled) {
dayElem.tabIndex = 0;
}
days.appendChild(dayElem);
}
// Start at 1 since there is no 0th day
for (dayNumber = 1; dayNumber <= daysInMonth; dayNumber++) {
currentDate = new Date(self.currentYear, self.currentMonth, dayNumber, 0, 0, 0, 0, 0);
if (self.config.weekNumbers && dayNumber % 7 === 1) {
weekNumbers.appendChild(
createElement("span", "disabled flatpickr-day", currentDate.fp_getWeek())
);
}
dateIsDisabled = !isEnabled(currentDate);
const dayElement = createElement(
"span",
dateIsDisabled ? "disabled" : "flatpickr-day",
dayNumber
);
if (!dateIsDisabled) {
dayElement.tabIndex = 0;
if (equalDates(currentDate, now)) {
dayElement.classList.add("today");
}
if (self.selectedDateObj && equalDates(currentDate, self.selectedDateObj)) {
dayElement.classList.add("selected");
}
}
days.appendChild(dayElement);
}
// append days from the next month
for (let dayNum = daysInMonth + 1; dayNum <= 42 - firstOfMonth; dayNum++) {
const curDate = new Date(
self.currentYear,
self.currentMonth + 1,
dayNum % daysInMonth,
0, 0, 0, 0, 0
),
dateIsEnabled = isEnabled(curDate),
dayElement = createElement(
"span",
dateIsEnabled ? "nextMonthDay flatpickr-day" : "disabled",
dayNum % daysInMonth
);
if (self.config.weekNumbers && dayNum % 7 === 1) {
weekNumbers.appendChild(
createElement("span", "disabled", curDate.fp_getWeek())
);
}
if (dateIsEnabled) {
dayElement.tabIndex = 0;
}
days.appendChild(dayElement);
}
calendar.appendChild(days);
};
buildTime = function () {
timeContainer = createElement("div", "flatpickr-time");
timeContainer.tabIndex = -1;
const separator = createElement("span", "flatpickr-time-separator", ":");
self.hourElement = createElement("input", "flatpickr-hour");
self.minuteElement = createElement("input", "flatpickr-minute");
self.hourElement.tabIndex = self.minuteElement.tabIndex = 0;
self.hourElement.type = self.minuteElement.type = "number";
self.hourElement.value =
self.selectedDateObj ? pad(self.selectedDateObj.getHours()) : 12;
self.minuteElement.value =
self.selectedDateObj ? pad(self.selectedDateObj.getMinutes()) : "00";
self.hourElement.step = self.config.hourIncrement;
self.minuteElement.step = self.config.minuteIncrement;
self.hourElement.min = -self.config.time_24hr;
self.hourElement.max = self.config.time_24hr ? 24 : 13;
self.minuteElement.min = -self.minuteElement.step;
self.minuteElement.max = 60;
self.hourElement.title = self.minuteElement.title = self.l10n.scrollTitle;
timeContainer.appendChild(self.hourElement);
timeContainer.appendChild(separator);
timeContainer.appendChild(self.minuteElement);
if (self.config.enableSeconds) {
timeContainer.classList.add("has-seconds");
self.secondElement = createElement("input", "flatpickr-second");
self.secondElement.type = "number";
self.secondElement.value =
self.selectedDateObj ? pad(self.selectedDateObj.getSeconds()) : "00";
self.secondElement.step = self.minuteElement.step;
self.secondElement.min = self.minuteElement.min;
self.secondElement.max = self.minuteElement.max;
timeContainer.appendChild(createElement("span", "flatpickr-time-separator", ":"));
timeContainer.appendChild(self.secondElement);
}
if (!self.config.time_24hr) { // add self.amPM if appropriate
self.amPM = createElement(
"span",
"flatpickr-am-pm",
["AM", "PM"][(self.hourElement.value > 11) | 0]
);
self.amPM.title = self.l10n.toggleTitle;
self.amPM.tabIndex = 0;
timeContainer.appendChild(self.amPM);
}
calendarContainer.appendChild(timeContainer);
};
bind = function () {
document.addEventListener("keydown", onKeyDown);
window.addEventListener("resize", onResize);
if (self.config.clickOpens) {
(self.altInput || self.input).addEventListener("click", self.open);
(self.altInput || self.input).addEventListener("focus", self.open);
}
if (self.config.wrap && self.element.querySelector("[data-open]")) {
self.element.querySelector("[data-open]").addEventListener("click", self.open);
}
if (self.config.wrap && self.element.querySelector("[data-close]")) {
self.element.querySelector("[data-close]").addEventListener("click", self.close);
}
if (self.config.wrap && self.element.querySelector("[data-toggle]")) {
self.element.querySelector("[data-toggle]").addEventListener("click", self.toggle);
}
if (self.config.wrap && self.element.querySelector("[data-clear]")) {
self.element.querySelector("[data-clear]").addEventListener("click", self.clear);
}
if (!self.config.noCalendar) {
prevMonthNav.addEventListener("click", () => {
changeMonth(-1);
});
nextMonthNav.addEventListener("click", () => {
changeMonth(1);
});
currentYearElement.addEventListener("wheel", yearScroll);
currentYearElement.addEventListener("focus", currentYearElement.select);
currentYearElement.addEventListener("input", event => {
self.currentYear = parseInt(event.target.value, 10);
self.redraw();
});
calendar.addEventListener("click", selectDate);
}
document.addEventListener("click", documentClick);
document.addEventListener("blur", documentClick, true);
if (self.config.enableTime) {
self.hourElement.addEventListener("wheel", timeWrapper);
self.minuteElement.addEventListener("wheel", timeWrapper);
self.hourElement.addEventListener("input", timeWrapper);
self.minuteElement.addEventListener("input", timeWrapper);
self.hourElement.addEventListener("mouseout", updateValue);
self.minuteElement.addEventListener("mouseout", updateValue);
self.hourElement.addEventListener("change", updateValue);
self.minuteElement.addEventListener("change", updateValue);
self.hourElement.addEventListener("focus", self.hourElement.select);
self.minuteElement.addEventListener("focus", self.minuteElement.select);
if (self.config.enableSeconds) {
self.secondElement.addEventListener("wheel", timeWrapper);
self.secondElement.addEventListener("input", timeWrapper);
self.secondElement.addEventListener("mouseout", updateValue);
self.secondElement.addEventListener("change", updateValue);
self.secondElement.addEventListener("focus", self.secondElement.select);
}
if (!self.config.time_24hr) {
self.amPM.addEventListener("click", amPMToggle);
self.amPM.addEventListener("wheel", amPMToggle);
self.amPM.addEventListener("mouseout", updateValue);
self.amPM.addEventListener("keydown", e => {
if (e.which === 38 || e.which === 40) {
amPMToggle(e);
}
});
}
}
if (document.createEvent) {
clickEvt = document.createEvent("MouseEvent");
// without all these args ms edge spergs out
clickEvt.initMouseEvent(
"click", true, true, window,
0, 0, 0, 0, 0, false, false,
false, false, 0, null
);
}
else {
clickEvt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
});
}
};
self.open = function () {
if (self.isOpen || (self.altInput || self.input).disabled || self.config.inline) {
return;
}
else if (!self.config.static) {
self.positionCalendar();
}
self.isOpen = true;
wrapperElement.classList.add("open");
if (!self.config.allowInput) {
(self.altInput || self.input).blur();
(self.config.noCalendar ? timeContainer : calendar).focus();
}
(self.altInput || self.input).classList.add("active");
if (self.config.onOpen) {
self.config.onOpen(self.selectedDateObj, self.input.value);
}
};
// For calendars inserted in BODY (as opposed to inline wrapper)
// it"s necessary to properly calculate top/left position.
self.positionCalendar = function () {
const calendarHeight = calendarContainer.offsetHeight,
input = (self.altInput || self.input),
inputBounds = input.getBoundingClientRect(),
distanceFromBottom = window.innerHeight - inputBounds.bottom + input.offsetHeight;
let top,
left = (window.pageXOffset + inputBounds.left);
if (distanceFromBottom < calendarHeight) {
top = (window.pageYOffset - calendarHeight + inputBounds.top) - 2;
calendarContainer.classList.remove("arrowTop");
calendarContainer.classList.add("arrowBottom");
}
else {
top = (window.pageYOffset + input.offsetHeight + inputBounds.top) + 2;
calendarContainer.classList.remove("arrowBottom");
calendarContainer.classList.add("arrowTop");
}
wrapperElement.style.top = `${top}px`;
wrapperElement.style.left = `${left}px`;
};
self.toggle = function () {
if (self.isOpen) {
self.close();
}
else {
self.open();
}
};
self.close = function () {
self.isOpen = false;
wrapperElement.classList.remove("open");
(self.altInput || self.input).classList.remove("active");
if (self.config.onClose) {
self.config.onClose(self.selectedDateObj, self.input.value);
}
};
self.clear = function () {
self.input.value = "";
if (self.altInput) {
self.altInput.value = "";
}
self.selectedDateObj = null;
triggerChange();
self.jumpToDate();
};
triggerChange = function () {
self.input.dispatchEvent(clickEvt);
if (self.config.onChange) {
self.config.onChange(self.selectedDateObj, self.input.value);
}
};
self.destroy = function () {
document.removeEventListener("click", documentClick, false);
if (self.altInput) {
self.altInput.parentNode.removeChild(self.altInput);
}
if (self.config.inline) {
const parent = self.element.parentNode,
removedElement = parent.removeChild(self.element);
parent.removeChild(calendarContainer);
parent.parentNode.replaceChild(removedElement, parent);
}
else {
document.getElementsByTagName("body")[0].removeChild(wrapperElement);
}
};
self.redraw = function () {
if (self.config.noCalendar) {
return;
}
updateNavigationCurrentMonth();
buildDays();
};
self.jumpToDate = function (jumpDate) {
jumpDate = uDate(
jumpDate || self.selectedDateObj || self.config.defaultDate ||
self.config.minDate || now
);
self.currentYear = jumpDate.getFullYear();
self.currentMonth = jumpDate.getMonth();
self.redraw();
};
self.setDate = function (date, triggerChangeEvent) {
date = uDate(date);
if (date instanceof Date && date.getTime()) {
self.selectedDateObj = uDate(date);
self.jumpToDate(self.selectedDateObj);
updateValue();
if (triggerChangeEvent) {
triggerChange();
}
}
};
self.setTime = function (hour, minute, triggerChangeEvent) {
if (!self.selectedDateObj) {
return;
}
self.hourElement.value = parseInt(hour, 10) % 24;
self.minuteElement.value = parseInt(minute || 0, 10) % 60;
if (!self.config.time_24hr) {
self.amPM.innerHTML = hour > 11 ? "PM" : "AM";
}
updateValue();
if (triggerChangeEvent) {
triggerChange();
}
};
self.set = function (key, value) {
if (key in self.config) {
self.config[key] = value;
self.jumpToDate();
}
};
amPMToggle = e => {
e.preventDefault();
self.amPM.textContent = ["AM", "PM"][(self.amPM.innerHTML === "AM") | 0];
};
onKeyDown = function (e) {
if (!self.isOpen || self.config.enableTime && timeContainer.contains(e.target)) {
return;
}
switch (e.which) {
case 13:
selectDate(e);
break;
case 27:
self.close();
break;
case 37:
changeMonth(-1);
break;
case 38:
e.preventDefault();
self.currentYear++;
self.redraw();
break;
case 39:
changeMonth(1);
break;
case 40:
e.preventDefault();
self.currentYear--;
self.redraw();
break;
default: break;
}
};
onResize = debounce(() => {
if (self.isOpen && !self.config.inline && !self.config.static) {
self.positionCalendar();
}
}, 300);
try {
init();
}
catch (error) {
// skip and carry on
console.error(error);
console.info(self.element);
}
return self;
};
flatpickr.init.prototype = {
defaultConfig: {},
l10n: {
weekdays: {
shorthand: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
longhand: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
},
months: {
shorthand: [
"Jan", "Feb", "Mar",
"Apr", "May", "Jun",
"Jul", "Aug", "Sep",
"Oct", "Nov", "Dec"
],
longhand: [
"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"
]
},
daysInMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
firstDayOfWeek: 0,
ordinal: (nth) => {
const s = nth % 100;
if (s > 3 && s < 21) return "th";
switch (s % 10) {
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
},
weekAbbreviation: "Wk",
scrollTitle: "Scroll to increment",
toggleTitle: "Click to toggle"
}
};
Date.prototype.fp_incr = function (days) {
return new Date(
this.getFullYear(),
this.getMonth(),
this.getDate() + parseInt(days, 10)
);
};
Date.prototype.fp_isUTC = false;
Date.prototype.fp_toUTC = function () {
const newDate = new Date(this.getTime() + this.getTimezoneOffset() * 60000);
newDate.fp_isUTC = true;
return newDate;
};
Date.prototype.fp_getWeek = function () {
const date = new Date(this.getTime());
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
// January 4 is always in week 1.
const week1 = new Date(date.getFullYear(), 0, 4);
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
return 1 +
Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 +
(week1.getDay() + 6) % 7) / 7);
};
// classList polyfill
if (!("classList" in document.documentElement) && Object.defineProperty &&
typeof HTMLElement !== "undefined") {
Object.defineProperty(HTMLElement.prototype, "classList", {
get: function () {
let selfElements = this;
function update(fn) {
return function (value) {
let classes = selfElements.className.split(/\s+/);
let index = classes.indexOf(value);
fn(classes, index, value);
selfElements.className = classes.join(" ");
};
}
let ret = {
add: update((classes, index, value) => ~index || classes.push(value)),
remove: update((classes, index) => ~index && classes.splice(index, 1)),
toggle: update((classes, index, value) => {
if (~index) {
classes.splice(index, 1);
}
else {
classes.push(value);
}
}),
contains: value => !!~selfElements.className.split(/\s+/).indexOf(value)
};
return ret;
}
});
}
if (typeof module !== "undefined") {
module.exports = flatpickr;
}