@mst101/vue-datepicker
Version:
A simple, but powerful, Vue 3 datepicker component. Supports disabling of dates, inline mode, translations & custom slots
1,676 lines • 133 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
import { openBlock, createElementBlock, normalizeClass, renderSlot, createElementVNode, createTextVNode, createCommentVNode, withKeys, withModifiers, Fragment, renderList, resolveComponent, createBlock, withCtx, toDisplayString, createVNode, Transition, h, withDirectives, resolveDynamicComponent, createSlots, vShow } from "vue";
class Language {
// eslint-disable-next-line max-params
constructor(language, months, monthsAbbr, days, rtl = false, ymd = false, yearSuffix = "") {
this.language = language;
this.months = months;
this.monthsAbbr = monthsAbbr;
this.days = days;
this.rtl = rtl;
this.ymd = ymd;
this.yearSuffix = yearSuffix;
}
/* eslint-disable no-underscore-dangle */
get language() {
return this._language;
}
set language(language) {
if (typeof language !== "string") {
throw new TypeError("Language must be a string");
}
this._language = language;
}
get months() {
return this._months;
}
set months(months) {
if (months.length !== 12) {
throw new RangeError(
`There must be 12 months for ${this.language} language`
);
}
this._months = months;
}
get monthsAbbr() {
return this._monthsAbbr;
}
set monthsAbbr(monthsAbbr) {
if (monthsAbbr.length !== 12) {
throw new RangeError(
`There must be 12 abbreviated months for ${this.language} language`
);
}
this._monthsAbbr = monthsAbbr;
}
get days() {
return this._days;
}
set days(days) {
if (days.length !== 7) {
throw new RangeError(`There must be 7 days for ${this.language} language`);
}
this._days = days;
}
getDaysStartingOn(firstDayOfWeek) {
const firstDays = this._days.slice(firstDayOfWeek);
const lastDays = this._days.slice(0, firstDayOfWeek);
return firstDays.concat(lastDays);
}
getMonthByAbbrName(name) {
const monthValue = this._monthsAbbr.findIndex((month) => month === name) + 1;
return monthValue < 10 ? `0${monthValue}` : `${monthValue}`;
}
getMonthByName(name) {
const monthValue = this._months.findIndex((month) => month === name) + 1;
return monthValue < 10 ? `0${monthValue}` : `${monthValue}`;
}
}
const en = new Language(
"English",
[
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
[
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
);
const calendarSlots = [
"beforeCalendarHeaderDay",
"calendarFooterDay",
"beforeCalendarHeaderMonth",
"calendarFooterMonth",
"beforeCalendarHeaderYear",
"calendarFooterYear",
"nextIntervalBtn",
"prevIntervalBtn"
];
const getParsableDate = ({
dateStr,
formatStr,
translation,
currentYear,
time
}) => {
const splitter = formatStr.match(/-|\/|\s|\./) || ["-"];
const df = formatStr.split(splitter[0]);
const ds = dateStr.split(splitter[0]);
const ymd = [currentYear.toString(), "01", "01"];
for (let i = 0; i < df.length; i += 1) {
if (/yyyy/i.test(df[i])) {
ymd[0] = ds[i];
} else if (/mmmm/i.test(df[i])) {
ymd[1] = translation.getMonthByName(ds[i]);
} else if (/mmm/i.test(df[i])) {
ymd[1] = translation.getMonthByAbbrName(ds[i]);
} else if (/mm/i.test(df[i])) {
ymd[1] = ds[i];
} else if (/m/i.test(df[i])) {
ymd[1] = ds[i];
} else if (/dd/i.test(df[i])) {
ymd[2] = ds[i];
} else if (/d/i.test(df[i])) {
const tmp = ds[i].replace(/st|rd|nd|th/g, "");
ymd[2] = tmp < 10 ? `0${tmp}` : `${tmp}`;
}
}
return `${ymd.join("-")}${time}`;
};
function parseDateWithLibrary(dateStr, format, parser) {
if (typeof parser !== "function") {
throw new Error("Parser needs to be a function");
}
if (typeof format !== "function") {
throw new Error("Format needs to be a function when using a custom parser");
}
return parser(dateStr);
}
const utils = {
/**
* @type {Boolean}
*/
useUtc: false,
/**
* Returns the full year, using UTC or not
* @param {Date} date
*/
getFullYear(date) {
return this.useUtc ? date.getUTCFullYear() : date.getFullYear();
},
/**
* Returns the month, using UTC or not
* @param {Date} date
*/
getMonth(date) {
return this.useUtc ? date.getUTCMonth() : date.getMonth();
},
/**
* Returns the number of days in the month, using UTC or not
* @param {Date} date
*/
getDaysInMonth(date) {
return this.daysInMonth(this.getFullYear(date), this.getMonth(date));
},
/**
* Returns the date, using UTC or not
* @param {Date} date
*/
getDate(date) {
return this.useUtc ? date.getUTCDate() : date.getDate();
},
/**
* Returns the day, using UTC or not
* @param {Date} date
*/
getDay(date) {
return this.useUtc ? date.getUTCDay() : date.getDay();
},
/**
* Returns the hours, using UTC or not
* @param {Date} date
*/
getHours(date) {
return this.useUtc ? date.getUTCHours() : date.getHours();
},
/**
* Returns the minutes, using UTC or not
* @param {Date} date
*/
getMinutes(date) {
return this.useUtc ? date.getUTCMinutes() : date.getMinutes();
},
/**
* Sets the full year, using UTC or not
* @param {Date} date
* @param {String, Number} value
*/
setFullYear(date, value) {
return this.useUtc ? date.setUTCFullYear(value) : date.setFullYear(value);
},
/**
* Sets the month, using UTC or not
* @param {Date} date
* @param {String, Number} value
*/
setMonth(date, value) {
return this.useUtc ? date.setUTCMonth(value) : date.setMonth(value);
},
/**
* Sets the date, using UTC or not
* @param {Date} date
* @param {String, Number} value
*/
setDate(date, value) {
return this.useUtc ? date.setUTCDate(value) : date.setDate(value);
},
/**
* Check if date1 is equivalent to date2, without comparing the time
* @see https://stackoverflow.com/a/6202196/4455925
* @param {Date|null} date1
* @param {Date|null} date2
*/
// eslint-disable-next-line complexity
compareDates(date1, date2) {
if (date1 === null && date2 === null) {
return true;
}
if (date1 !== null && date2 === null || date2 !== null && date1 === null) {
return false;
}
const d1 = new Date(date1.valueOf());
const d2 = new Date(date2.valueOf());
this.resetDateTime(d1);
this.resetDateTime(d2);
return d1.valueOf() === d2.valueOf();
},
/**
* Validates a date object
* @param {Date} date - an object instantiated with the new Date constructor
* @return {Boolean}
*/
isValidDate(date) {
if (Object.prototype.toString.call(date) !== "[object Date]") {
return false;
}
return !Number.isNaN(date.valueOf());
},
/**
* Return abbreviated week day name
* @param {Date} date
* @param {Array} days
* @return {String}
*/
getDayNameAbbr(date, days) {
if (typeof date !== "object") {
throw TypeError("Invalid Type");
}
return days[this.getDay(date)];
},
/**
* Return day number from abbreviated week day name
* @param {String} abbr
* @return {Number}
*/
getDayFromAbbr(abbr) {
for (let i = 0; i < en.days.length; i += 1) {
if (abbr.toLowerCase() === en.days[i].toLowerCase()) {
return i;
}
}
throw TypeError("Invalid week day");
},
/**
* Return name of the month
* @param {Number|Date} month
* @param {Array} months
* @return {String}
*/
getMonthName(month, months) {
if (!months) {
throw Error("missing 2nd parameter Months array");
}
if (typeof month === "object") {
return months[this.getMonth(month)];
}
if (typeof month === "number") {
return months[month];
}
throw TypeError("Invalid type");
},
/**
* Return an abbreviated version of the month
* @param {Number|Date} month
* @param {Array} monthsAbbr
* @return {String}
*/
getMonthNameAbbr(month, monthsAbbr) {
if (!monthsAbbr) {
throw Error("missing 2nd parameter Months array");
}
if (typeof month === "object") {
return monthsAbbr[this.getMonth(month)];
}
if (typeof month === "number") {
return monthsAbbr[month];
}
throw TypeError("Invalid type");
},
/**
* Alternative get total number of days in month
* @param {Number} year
* @param {Number} month
* @return {Number}
*/
// eslint-disable-next-line complexity
daysInMonth(year, month) {
if (/8|3|5|10/.test(month.toString())) {
return 30;
}
if (month === 1) {
return !(year % 4) && year % 100 || !(year % 400) ? 29 : 28;
}
return 31;
},
/**
* Get nth suffix for date
* @param {Number} day
* @return {String}
*/
// eslint-disable-next-line complexity
getNthSuffix(day) {
switch (day) {
case 1:
case 21:
case 31:
return "st";
case 2:
case 22:
return "nd";
case 3:
case 23:
return "rd";
default:
return "th";
}
},
/**
* Formats date object
* @param {Date} date
* @param {String} formatStr
* @param {Object} translation
* @return {String}
*/
formatDate(date, formatStr, translation = en) {
const year = this.getFullYear(date);
const month = this.getMonth(date) + 1;
const day = this.getDate(date);
const matches = {
d: day,
dd: `0${day}`.slice(-2),
E: this.getDayNameAbbr(date, translation.days),
o: this.getNthSuffix(this.getDate(date)),
M: month,
MM: `0${month}`.slice(-2),
MMM: this.getMonthNameAbbr(this.getMonth(date), translation.monthsAbbr),
MMMM: this.getMonthName(this.getMonth(date), translation.months),
yy: String(year).slice(2),
yyyy: year
};
const REGEX_FORMAT = /y{4}|y{2}|M{1,4}|d{1,2}|o|E/g;
return formatStr.replace(REGEX_FORMAT, (match) => matches[match]);
},
/**
* Parses a date from a string, or returns the original string
* @param {String} dateStr
* @param {String|Function} format
* @param {Object} translation
* @param {Function} parser
* @return {Date | String}
*/
// eslint-disable-next-line max-params
parseDate(dateStr, format, translation = en, parser = null) {
if (!(dateStr && format)) {
return dateStr;
}
if (parser) {
return parseDateWithLibrary(dateStr, format, parser);
}
const parseableDate = getParsableDate({
dateStr,
formatStr: format,
translation,
currentYear: this.getFullYear(/* @__PURE__ */ new Date()),
time: this.getTime()
});
const parsedDate = Date.parse(parseableDate);
if (Number.isNaN(parsedDate)) {
return dateStr;
}
return new Date(parsedDate);
},
/**
* Parses a string/number to a date, or returns null
* @param {Date|String|Number|undefined} date
* @returns {Date|null}
*/
parseAsDate(date) {
if (typeof date === "string" || typeof date === "number") {
const parsed = new Date(date);
return this.isValidDate(parsed) ? parsed : null;
}
return this.isValidDate(date) ? date : null;
},
getTime() {
const time = "T00:00:00";
return this.useUtc ? `${time}Z` : time;
},
/**
* Remove hours/minutes/seconds/milliseconds from a date object
* @param {Date} date
* @return {Date}
*/
resetDateTime(date) {
return new Date(
this.useUtc ? date.setUTCHours(0, 0, 0, 0) : date.setHours(0, 0, 0, 0)
);
},
/**
* Return a new date object with hours/minutes/seconds/milliseconds removed.
* Defaults to today's date, if no parameter is provided
* @param {Date=} date
* @return {Date}
*/
getNewDateObject(date) {
return date ? this.resetDateTime(new Date(date)) : this.resetDateTime(/* @__PURE__ */ new Date());
},
/**
* Returns the `open date` at a given view
* @param {Date|null} openDate the date on which the datepicker should open
* @param {View} view Either `day`, `month`, or `year`
* @return {Date|null}
*/
getOpenDate(openDate, selectedDate, view) {
const parsedOpenDate = this.parseAsDate(openDate);
const openDateOrToday = this.getNewDateObject(parsedOpenDate);
const newOpenDate = selectedDate || openDateOrToday;
return this.adjustDateToView(newOpenDate, view);
},
/**
* Converts a date according to a given view
* e.g. '2025-05-15' becomes '2025-05-01' at `month view and
* '2025-01-01' at `year` view
* @param {Date} dateToConvert The date to convert
* @param {String} view The view for which to adjust the date
* @return {Date}
*/
adjustDateToView(dateToConvert, view) {
const date = this.getNewDateObject(dateToConvert);
if (view === "year") {
const resetDay = new Date(this.setDate(date, 1));
const resetMonth = this.setMonth(resetDay, 0);
return new Date(resetMonth);
}
if (view === "month") {
return new Date(this.setDate(date, 1));
}
return date;
}
};
const makeDateUtils = (useUtc) => __spreadProps(__spreadValues({}, utils), {
useUtc
});
const _sfc_main$a = {
props: {
autofocus: {
type: Boolean,
default: false
},
bootstrapStyling: {
type: Boolean,
default: false
},
clearButton: {
type: Boolean,
default: null
},
calendarButton: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
format: {
type: [String, Function],
default: "dd MMM yyyy"
},
id: {
type: String,
default: null
},
inline: {
type: Boolean,
default: false
},
inputClass: {
type: [String, Object, Array],
default: null
},
maxlength: {
type: [Number, String],
default: null
},
name: {
type: String,
default: null
},
openDate: {
type: [String, Date, Number],
default: null
},
parser: {
type: Function,
default: null
},
pattern: {
type: String,
default: null
},
placeholder: {
type: String,
default: null
},
refName: {
type: String,
default: ""
},
required: {
type: Boolean,
default: false
},
showCalendarOnButtonClick: {
type: Boolean,
default: false
},
showCalendarOnFocus: {
type: Boolean,
default: false
},
tabindex: {
type: [Number, String],
default: null
},
typeable: {
type: Boolean,
default: false
},
useUtc: {
type: Boolean,
default: false
}
}
};
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$9 = {
name: "DateInput",
mixins: [_sfc_main$a],
props: {
isOpen: {
type: Boolean,
default: false
},
selectedDate: {
type: Date,
default: null
},
slideDuration: {
type: Number,
default: 250
},
translation: {
type: Object,
default() {
return {};
}
}
},
emits: {
blur: null,
clearDate: null,
close: null,
focus: null,
open: null,
selectTypedDate: (date) => {
return date === null || date instanceof Date;
},
setFocus: (refArray) => {
return refArray.every((ref) => {
return [
"calendarButton",
"input",
"prev",
"up",
"next",
"tabbableCell"
].includes(ref);
});
},
tab: null,
typedDate: (date) => {
return date === null || date instanceof Date;
}
},
data() {
return {
input: null,
isInputFocused: false,
shouldToggleOnFocus: false,
shouldToggleOnClick: true,
typedDate: "",
utils: makeDateUtils(this.useUtc)
};
},
computed: {
computedInputClass() {
if (this.bootstrapStyling) {
if (typeof this.inputClass === "string") {
return [this.inputClass, "form-control"].join(" ");
}
return __spreadValues({ "form-control": true }, this.inputClass);
}
return this.inputClass;
},
formattedValue() {
if (this.typeable) {
return this.typedDate;
}
return this.formatDate(this.selectedDate);
}
},
watch: {
showCalendarOnFocus: {
immediate: true,
handler(showCalendarOnFocus) {
if (showCalendarOnFocus) {
this.shouldToggleOnFocus = !this.isOpen;
}
}
},
isOpen(isOpen, wasOpen) {
this.$nextTick(() => {
if (isOpen && this.showCalendarOnFocus) {
if (wasOpen && !this.isInputFocused) {
this.shouldToggleOnFocus = true;
return;
}
this.shouldToggleOnFocus = false;
}
});
},
selectedDate: {
immediate: true,
handler(selectedDate) {
if (this.typeable) {
this.typedDate = this.formatDate(selectedDate);
}
}
}
},
mounted() {
this.input = this.$el.querySelector("input");
},
methods: {
/**
* Emits a `clear-date` event
*/
clearDate() {
this.input.value = "";
this.$emit("clearDate");
},
/**
* Formats a date
* @param {Date} date The date to be formatted
* @returns {String}
*/
formatDate(date) {
if (!date) {
return "";
}
return typeof this.format === "function" ? this.format(new Date(date)) : this.utils.formatDate(new Date(date), this.format, this.translation);
},
/**
* Formats a typed date, or clears it if invalid
*/
formatTypedDate() {
const parsedDate = this.parseInput();
if (this.utils.isValidDate(parsedDate)) {
this.typedDate = this.formatDate(parsedDate);
} else {
this.input.value = "";
this.typedDate = "";
}
},
/**
* Validates typedDate
*/
handleInputBlur() {
if (this.showCalendarOnFocus && !this.isOpen) {
this.shouldToggleOnFocus = true;
}
if (this.typeable) {
this.formatTypedDate();
}
this.isInputFocused = false;
},
/**
* Resets `shouldToggleOnFocus` to true
*/
handleButtonFocus() {
if (this.showCalendarOnFocus) {
this.shouldToggleOnFocus = true;
}
},
/**
* Clears the calendar when the `delete` or `backspace` key is pressed
*/
handleDelete() {
if (!this.typeable && this.selectedDate) {
this.clearDate();
}
},
/**
* Toggles the calendar (unless `show-calendar-on-button-click` is true)
*/
handleInputClick() {
if (this.showCalendarOnButtonClick)
return;
if (this.shouldToggleOnClick) {
this.toggle();
}
},
/**
* Opens the calendar when `show-calendar-on-focus` is true (unless `show-calendar-on-button-click` is true)
*/
// eslint-disable-next-line complexity
handleInputFocus() {
if (this.showCalendarOnButtonClick)
return;
this.isInputFocused = true;
if (!this.isOpen && this.shouldToggleOnFocus) {
this.shouldToggleOnClick = false;
}
if (this.shouldToggleOnFocus && !this.isOpen) {
this.$emit("open");
setTimeout(() => {
this.shouldToggleOnClick = true;
}, this.slideDuration);
}
},
/**
* Opens the calendar, or sets the focus to the next focusable element down
*/
handleKeydownDown() {
if (!this.isOpen) {
this.$emit("open");
}
if (!this.typeable) {
return;
}
this.$emit("setFocus", ["prev", "up", "next", "tabbableCell"]);
},
/**
* Selects a typed date and closes the calendar
*/
handleKeydownEnter() {
if (!this.typeable) {
return;
}
if (!this.input.value) {
this.$emit("selectTypedDate", null);
return;
}
const parsedDate = this.parseInput();
if (this.utils.isValidDate(parsedDate)) {
this.$emit("selectTypedDate", parsedDate);
}
},
/**
* Closes the calendar
*/
handleKeydownEscape() {
if (this.isOpen) {
this.$emit("close");
}
},
/**
* Prevents scrolling when not typeable
*/
handleKeydownSpace(event) {
if (!this.typeable) {
event.preventDefault();
}
},
/**
* Parses a typed date and emits `typed-date` event, if valid
* @param {object} event Used to exclude certain keystrokes
*/
handleKeyup(event) {
if (!this.typeable || [
"Control",
"Escape",
"Shift",
"Tab",
"ArrowUp",
"ArrowDown",
"ArrowLeft",
"ArrowRight"
].includes(event.key)) {
return;
}
this.typedDate = this.input.value;
if (!this.input.value) {
this.$emit("typedDate", null);
return;
}
const parsedDate = this.parseInput();
if (this.utils.isValidDate(parsedDate)) {
this.$emit("typedDate", parsedDate);
}
},
/**
* Toggles the calendar unless a typed date has been entered or `show-calendar-on-button-click` is true
*/
handleKeyupSpace(event) {
if (this.typeable) {
if (this.input.value === "") {
this.toggle();
}
return;
}
event.preventDefault();
if (!this.showCalendarOnButtonClick) {
this.toggle();
}
},
/**
* Parses the value of the input field
*/
parseInput() {
return new Date(
this.utils.parseDate(
this.input.value.trim(),
this.format,
this.translation,
this.parser
)
);
},
/**
* Opens or closes the calendar
*/
toggle(calendarButton) {
if (this.isOpen) {
this.$emit("setFocus", [calendarButton || "input"]);
}
this.$emit(this.isOpen ? "close" : "open");
}
}
};
const _hoisted_1$6 = ["disabled"];
const _hoisted_2$6 = ["id", "autofocus", "clear-button", "disabled", "maxlength", "name", "pattern", "placeholder", "readonly", "required", "tabindex", "type", "value"];
const _hoisted_3$5 = ["disabled"];
function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
return openBlock(), createElementBlock("div", {
class: normalizeClass({ "input-group": _ctx.bootstrapStyling })
}, [
renderSlot(_ctx.$slots, "beforeDateInput"),
_ctx.calendarButton ? (openBlock(), createElementBlock("button", {
key: 0,
ref: "calendarButton",
class: normalizeClass(["vdp-datepicker__calendar-button", { "btn input-group-prepend": _ctx.bootstrapStyling }]),
"data-test-calendar-button": "",
disabled: _ctx.disabled,
type: "button",
onClick: _cache[0] || (_cache[0] = ($event) => $options.toggle("calendarButton")),
onFocus: _cache[1] || (_cache[1] = (...args) => $options.handleButtonFocus && $options.handleButtonFocus(...args))
}, [
createElementVNode("span", {
class: normalizeClass({ "input-group-text": _ctx.bootstrapStyling })
}, [
renderSlot(_ctx.$slots, "calendarBtn", {}, () => [
createTextVNode("…")
])
], 2)
], 42, _hoisted_1$6)) : createCommentVNode("", true),
createElementVNode("input", {
id: _ctx.id,
ref: _ctx.refName,
autocomplete: "off",
autofocus: _ctx.autofocus,
class: normalizeClass($options.computedInputClass),
"clear-button": _ctx.clearButton,
"data-test-input": "",
disabled: _ctx.disabled,
maxlength: _ctx.maxlength,
name: _ctx.name,
pattern: _ctx.pattern,
placeholder: _ctx.placeholder,
readonly: !_ctx.typeable,
required: _ctx.required,
tabindex: _ctx.tabindex,
type: _ctx.inline ? "hidden" : null,
value: $options.formattedValue,
onBlur: _cache[2] || (_cache[2] = (...args) => $options.handleInputBlur && $options.handleInputBlur(...args)),
onClick: _cache[3] || (_cache[3] = (...args) => $options.handleInputClick && $options.handleInputClick(...args)),
onFocus: _cache[4] || (_cache[4] = (...args) => $options.handleInputFocus && $options.handleInputFocus(...args)),
onKeydown: [
_cache[5] || (_cache[5] = withKeys((...args) => $options.handleDelete && $options.handleDelete(...args), ["delete"])),
_cache[6] || (_cache[6] = withKeys(withModifiers((...args) => $options.handleKeydownDown && $options.handleKeydownDown(...args), ["prevent"]), ["down"])),
_cache[7] || (_cache[7] = withKeys(withModifiers((...args) => $options.handleKeydownEnter && $options.handleKeydownEnter(...args), ["prevent"]), ["enter"])),
_cache[8] || (_cache[8] = withKeys(withModifiers((...args) => $options.handleKeydownEscape && $options.handleKeydownEscape(...args), ["prevent"]), ["esc"])),
_cache[9] || (_cache[9] = withKeys(($event) => $options.handleKeydownSpace($event), ["space"])),
_cache[10] || (_cache[10] = withKeys(($event) => _ctx.$emit("tab", $event), ["tab"]))
],
onKeyup: [
_cache[11] || (_cache[11] = ($event) => $options.handleKeyup($event)),
_cache[12] || (_cache[12] = withKeys(($event) => $options.handleKeyupSpace($event), ["space"]))
]
}, null, 42, _hoisted_2$6),
_ctx.clearButton && $props.selectedDate ? (openBlock(), createElementBlock("button", {
key: 1,
class: normalizeClass(["vdp-datepicker__clear-button", { "btn input-group-append": _ctx.bootstrapStyling }]),
"data-test-clear-button": "",
disabled: _ctx.disabled,
type: "button",
onClick: _cache[13] || (_cache[13] = (...args) => $options.clearDate && $options.clearDate(...args))
}, [
createElementVNode("span", {
class: normalizeClass({ "input-group-text": _ctx.bootstrapStyling })
}, [
renderSlot(_ctx.$slots, "clearBtn", {}, () => [
createTextVNode("×")
])
], 2)
], 10, _hoisted_3$5)) : createCommentVNode("", true),
renderSlot(_ctx.$slots, "afterDateInput")
], 2);
}
const DateInput = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["render", _sfc_render$6]]);
const cellUtils = {
isDefined(obj, prop) {
return obj && typeof obj[prop] !== "undefined";
},
hasArray(obj, prop) {
return obj && Array.isArray(obj[prop]);
},
hasDate(obj, prop) {
return this.isDefined(obj, prop) && this.utils.isValidDate(obj[prop]);
},
dayMonthYear(obj, prop) {
const { utils: utils2 } = this;
const hasDate = this.hasDate(obj, prop);
if (!hasDate) {
return {
day: void 0,
month: void 0,
year: void 0
};
}
const d = obj[prop];
return {
day: utils2.getDate(d),
month: utils2.getMonth(d),
year: utils2.getFullYear(d)
};
}
};
const makeCellUtils = (utils2) => __spreadProps(__spreadValues({}, cellUtils), {
utils: utils2
});
class DisabledDate {
constructor(utils2, disabledDates) {
this._utils = utils2;
this._disabledDates = disabledDates;
}
get config() {
const disabledDates = this._disabledDates;
const utils2 = makeCellUtils(this._utils);
const has = {
customPredictor: utils2.isDefined(disabledDates, "customPredictor"),
daysOfMonth: utils2.hasArray(disabledDates, "daysOfMonth"),
daysOfWeek: utils2.hasArray(disabledDates, "days"),
from: utils2.hasDate(disabledDates, "from"),
ranges: utils2.hasArray(disabledDates, "ranges"),
specificDates: utils2.hasArray(disabledDates, "dates"),
to: utils2.hasDate(disabledDates, "to")
};
return {
to: utils2.dayMonthYear(disabledDates, "to"),
from: utils2.dayMonthYear(disabledDates, "from"),
has
};
}
daysInMonth(date) {
const utils2 = this._utils;
const month = utils2.getMonth(date);
const year = utils2.getFullYear(date);
return utils2.daysInMonth(year, month);
}
isDateDisabledVia(date) {
const disabledDates = this._disabledDates;
const { has } = this.config;
return {
to: () => {
return has.to && date <= disabledDates.to;
},
from: () => {
return has.from && date >= disabledDates.from;
},
range: () => {
if (!has.ranges)
return false;
const { ranges } = disabledDates;
const u = makeCellUtils(this._utils);
return ranges.some((thisRange) => {
const hasFrom = u.isDefined(thisRange, "from");
const hasTo = u.isDefined(thisRange, "to");
return hasFrom && hasTo && date <= thisRange.to && date >= thisRange.from;
});
},
customPredictor: () => {
return has.customPredictor && disabledDates.customPredictor(date);
},
specificDate: () => {
if (!has.specificDates)
return false;
return disabledDates.dates.some((d) => {
return this._utils.compareDates(date, d);
});
},
daysOfWeek: () => {
if (!has.daysOfWeek)
return false;
return disabledDates.days.indexOf(this._utils.getDay(date)) !== -1;
},
daysOfMonth: () => {
if (!has.daysOfMonth)
return false;
return disabledDates.daysOfMonth.indexOf(this._utils.getDate(date)) !== -1;
}
};
}
isMonthDisabledVia(date) {
const { from, has, to } = this.config;
const month = this._utils.getMonth(date);
const year = this._utils.getFullYear(date);
return {
to: () => {
const isYearInPast = has.to && year < to.year;
if (isYearInPast) {
return true;
}
return has.to && month < to.month && year <= to.year;
},
from: () => {
const isYearInFuture = has.from && year > from.year;
if (isYearInFuture) {
return true;
}
return has.from && month > from.month && year >= from.year;
}
};
}
isYearDisabledVia(date) {
const { from, has, to } = this.config;
const year = this._utils.getFullYear(date);
return {
to: () => {
return has.to && year < to.year;
},
from: () => {
return has.from && year > from.year;
}
};
}
/**
* Checks if the given date should be disabled
* @param {Date} date
* @return {Boolean}
*/
// eslint-disable-next-line complexity,max-statements
isDateDisabled(date) {
const isDisabledVia = this.isDateDisabledVia(date);
return isDisabledVia.to() || isDisabledVia.from() || isDisabledVia.range() || isDisabledVia.specificDate() || isDisabledVia.daysOfWeek() || isDisabledVia.daysOfMonth() || isDisabledVia.customPredictor();
}
/**
* Checks if the given month should be disabled
* @param {Date} date
* @return {Boolean}
*/
// eslint-disable-next-line complexity,max-statements
isMonthDisabled(date) {
const isDisabledVia = this.isMonthDisabledVia(date);
if (isDisabledVia.to() || isDisabledVia.from()) {
return true;
}
for (let i = 1; i <= this.daysInMonth(date); i += 1) {
const dayDate = new Date(date);
dayDate.setDate(i);
if (!this.isDateDisabled(dayDate)) {
return false;
}
}
return true;
}
/**
* Checks if the given year should be disabled
* @param {Date} date
* @return {Boolean}
*/
// eslint-disable-next-line complexity,max-statements
isYearDisabled(date) {
const isDisabledVia = this.isYearDisabledVia(date);
if (isDisabledVia.to() || isDisabledVia.from()) {
return true;
}
for (let i = 0; i < 12; i += 1) {
const monthDate = new Date(date);
monthDate.setMonth(i);
if (!this.isMonthDisabled(monthDate)) {
return false;
}
}
return true;
}
getEarliestPossibleDate(date) {
if (!date) {
return null;
}
const utils2 = this._utils;
if (this.isDateDisabled(date)) {
const nextDate = new Date(
utils2.getFullYear(date),
utils2.getMonth(date),
utils2.getDate(date) + 1
);
return this.getEarliestPossibleDate(nextDate);
}
return date;
}
getLatestPossibleDate(date) {
if (!date) {
return null;
}
const utils2 = this._utils;
if (this.isDateDisabled(date)) {
const nextDate = new Date(
utils2.getFullYear(date),
utils2.getMonth(date),
utils2.getDate(date) - 1
);
return this.getLatestPossibleDate(nextDate);
}
return date;
}
}
const _sfc_main$8 = {
data() {
return {
focus: {
delay: 0,
refs: []
},
inlineTabbableCell: null,
isActive: false,
isRevertingToOpenDate: false,
navElements: [],
navElementsFocusedIndex: 0,
resetTabbableCell: false,
skipReviewFocus: false,
tabbableCell: null,
transitionName: ""
};
},
computed: {
fallbackElementsToFocus() {
const elements = ["tabbableCell", "prev", "next"];
if (this.typeable) {
elements.unshift("input");
}
return elements;
},
focusedDateTimestamp() {
const pageDate = new Date(this.pageTimestamp);
if (this.hasClass(this.tabbableCell, "day")) {
return this.utils.setDate(pageDate, this.tabbableCell.innerHTML.trim());
}
if (this.hasClass(this.tabbableCell, "month")) {
return this.utils.setMonth(pageDate, this.tabbableCellId);
}
const fullYear = this.utils.getFullYear(pageDate) - 1;
return this.utils.setFullYear(pageDate, fullYear + this.tabbableCellId);
},
tabbableCellId() {
return this.tabbableCell && Number(this.tabbableCell.getAttribute("data-id"));
}
},
methods: {
/**
* Converts a date to first in month for `month` view or first in year for `year` view
* @param {Date} date The date to convert
* @returns {Date}
*/
getCellDate(date) {
switch (this.view) {
case "month":
return new Date(this.utils.setDate(date, 1));
case "year":
return new Date(
this.utils.setMonth(new Date(this.utils.setDate(date, 1)), 0)
);
default:
return date;
}
},
/**
* Returns true, unless tabbing should be focus-trapped
* @return {Boolean}
*/
allowNormalTabbing(event) {
if (!this.isOpen) {
return true;
}
return this.isTabbingAwayFromInlineDatepicker(event);
},
/**
* Focuses the first non-disabled element found in the `focus.refs` array and sets `navElementsFocusedIndex`
*/
applyFocus() {
const focusRefs = [...this.focus.refs, ...this.fallbackElementsToFocus];
for (let i = 0; i < focusRefs.length; i += 1) {
const element = this.getElementByRef(focusRefs[i]);
if (element && element.getAttribute("disabled") === null) {
element.focus();
this.setNavElementsFocusedIndex();
break;
}
}
},
/**
* Ensures the most recently focused tabbable cell is focused when tabbing backwards to an inline calendar
* If no element has previously been focused, the tabbable cell is reset and focused
*/
focusInlineTabbableCell() {
if (this.inlineTabbableCell) {
this.inlineTabbableCell.focus();
return;
}
this.resetTabbableCell = true;
this.setTabbableCell();
this.tabbableCell.focus();
this.resetTabbableCell = false;
},
/**
* Returns the currently focused cell element, if there is one...
*/
getActiveCell() {
const activeElement = this.getActiveElement();
const isActiveElementACell = this.hasClass(activeElement, "cell");
const isOnSameView = this.hasClass(activeElement, this.view);
if (isActiveElementACell && isOnSameView && !this.resetTabbableCell) {
return activeElement;
}
return null;
},
/**
* Returns the currently focused element, using shadowRoot for web-components...
*/
getActiveElement() {
return document.activeElement.shadowRoot ? document.activeElement.shadowRoot.activeElement : document.activeElement;
},
/**
* Returns the `cellId` for a given a date
* @param {Date} date The date for which we need the cellId
* @returns {Number|null}
*/
getCellId(date) {
if (!date || !this.$refs.picker.$refs.cells) {
return null;
}
const cellDate = this.getCellDate(date);
const { cells } = this.$refs.picker.$refs.cells;
for (let i = 0; i < cells.length; i += 1) {
if (cells[i].timestamp === cellDate.valueOf()) {
return i;
}
}
return null;
},
/**
* Finds an element by its `ref` attribute
* @param {string} ref The `ref` name of the wanted element
* @returns {HTMLElement|Vue} A Vue element
*/
// eslint-disable-next-line complexity,max-statements
getElementByRef(ref) {
if (ref === "tabbableCell") {
return this.tabbableCell;
}
if (ref === "input") {
return this.$refs.dateInput && this.$refs.dateInput.$refs[this.refName];
}
if (ref === "calendarButton") {
return this.$refs.dateInput && this.$refs.dateInput.$refs.calendarButton;
}
if (ref === "openDate") {
return this.$refs.picker.$refs.cells.$refs.openDate[0];
}
if (this.showHeader) {
return this.$refs.picker && this.$refs.picker.$refs.pickerHeader && this.$refs.picker.$refs.pickerHeader.$refs[ref];
}
return null;
},
/**
* Returns an array of all HTML elements which should be focus-trapped in the calendarFooter slot
* @returns {Array} An array of HTML elements
*/
getElementsFromCalendarFooter() {
const footerSlotIndex = this.hasSlot("beforeCalendarHeader") ? 2 : 1;
return this.getFocusableElements(
this.$refs.view.children[footerSlotIndex]
);
},
/**
* Returns an array of all HTML elements which should be focus-trapped in the specified slot
* @returns {Array} An array of HTML elements
*/
getElementsFromSlot(slotName) {
if (!this.hasSlot(slotName)) {
return [];
}
if (slotName === "beforeCalendarHeader") {
return this.getFocusableElements(this.$refs.view.children[0]);
}
if (slotName === "calendarFooter") {
return this.getElementsFromCalendarFooter();
}
const isBeforeHeader = slotName.indexOf("beforeCalendarHeader") > -1;
const picker = this.$refs.picker.$el;
const index = isBeforeHeader ? 0 : picker.children.length - 1;
return this.getFocusableElements(picker.children[index]);
},
/**
* Returns an array of all HTMLButtonElements which should be focus-trapped in the header
* @returns {Array} An array of HTMLButtonElements
*/
getElementsFromHeader() {
if (!this.$refs.picker.$refs.pickerHeader) {
return [];
}
const header = this.$refs.picker.$refs.pickerHeader.$el;
const navNodeList = header.querySelectorAll("button:enabled");
return [...Array.prototype.slice.call(navNodeList)];
},
/**
* Returns an array of focusable elements in a given HTML fragment
* @param {Element} fragment The HTML fragment to search
* @returns {Array}
*/
getFocusableElements(fragment) {
if (!fragment) {
return [];
}
const navNodeList = fragment.querySelectorAll(
'button:enabled:not([tabindex="-1"]), [href]:not([tabindex="-1"]), input:not([tabindex="-1"]):not([type=hidden]), select:enabled:not([tabindex="-1"]), textarea:enabled:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])'
);
return [...Array.prototype.slice.call(navNodeList)];
},
/**
* Returns the first focusable element of an inline datepicker
* @returns {HTMLElement}
*/
getFirstInlineFocusableElement() {
const popupElements = this.getFocusableElements(this.$refs.popup.$el);
return popupElements[0];
},
/**
* Returns the last focusable element of an inline datepicker
* @returns {HTMLElement}
*/
getLastInlineFocusableElement() {
const popupElements = this.getFocusableElements(this.$refs.popup.$el);
return popupElements[popupElements.length - 1];
},
/**
* Returns the input element (when typeable)
* @returns {Element}
*/
getInputField() {
if (!this.typeable || this.inline) {
return null;
}
return this.$refs.dateInput.$refs[this.refName];
},
/**
* Used for a typeable datepicker: returns the cell element that corresponds to latestValidTypedDate...
*/
getTypedCell() {
if (!this.typeable) {
return null;
}
const cellId = this.getCellId(this.latestValidTypedDate);
return cellId ? this.$refs.picker.$refs.cells.$el.children[cellId] : null;
},
/**
* Sets `datepickerId` (as a global) and keeps track of focusable elements
*/
handleFocusIn() {
document.datepickerId = this.datepickerId;
this.globalDatepickerId = this.datepickerId;
this.isActive = true;
this.setInlineTabbableCell();
this.setNavElements();
},
/**
* Sets the datepicker's `isActive` state to false and resets `globalDatepickerId`
*/
handleFocusOut() {
this.isActive = false;
this.globalDatepickerId = "";
},
/**
* Returns true if the calendar has been passed the given slot
* @param {String} slotName The name of the slot
* @return {Boolean}
*/
hasSlot(slotName) {
return !!this.$slots[slotName];
},
/**
* Returns true if the user is tabbing away from an inline datepicker
* @return {Boolean}
*/
isTabbingAwayFromInlineDatepicker(event) {
if (!this.inline) {
return false;
}
if (this.isTabbingAwayFromFirstNavElement(event)) {
this.tabAwayFromFirstElement();
return true;
}
if (this.isTabbingAwayFromLastNavElement(event)) {
this.tabAwayFromLastElement();
return true;
}
return false;
},
/**
* Used for inline calendars; returns true if the user tabs backwards from the first focusable element
* @param {object} event Used to determine whether we are tabbing forwards or backwards
* @return {Boolean}
*/
isTabbingAwayFromFirstNavElement(event) {
if (!event.shiftKey) {
return false;
}
const activeElement = this.getActiveElement();
const firstNavElement = this.navElements[0];
return activeElement === firstNavElement;
},
/**
* Used for inline calendars; returns true if the user tabs forwards from the last focusable element
* @param {object} event Used to determine whether we are tabbing forwards or backwards
* @return {Boolean}
*/
isTabbingAwayFromLastNavElement(event) {
if (event.shiftKey) {
return false;
}
const activeElement = this.getActiveElement();
const lastNavElement = this.navElements[this.navElements.length - 1];
return activeElement === lastNavElement;
},
/**
* Resets the focus to the open date
*/
resetFocusToOpenDate(focusedDateTimestamp) {
this.focus.refs = ["openDate"];
this.setTransitionAndFocusDelay(
focusedDateTimestamp,
this.computedOpenDate
);
if (!this.isMinimumView) {
this.isRevertingToOpenDate = true;
this.view = this.minimumView;
}
this.setPageDate(this.computedOpenDate);
this.reviewFocus();
},
/**
* Sets the correct focus on next tick
*/
reviewFocus() {
if (this.skipReviewFocus) {
return;
}
this.tabbableCell = null;
this.resetTabbableCell = true;
this.$nextTick(() => {
this.setNavElements();
setTimeout(() => {
this.applyFocus();
}, this.focus.delay);
this.resetTabbableCell = false;
});
},
/**
* Stores the current tabbableCell of an inline datepicker
* N.B. This is used when tabbing back (shift + tab) to an inline calendar from further down the page
*/
setInlineTabbableCell() {
if (!this.inline) {
return;
}
this.inlineTabbableCell = this.tabbableCell;
},
/**
* Sets the direction of the slide transition and whether or not to delay application of the focus
* @param {Date|Number} startDate The date from which to measure
* @param {Date|Number} endDate Is this before or after the startDate? And is it on the same page?
*/
setTransitionAndFocusDelay(startDate, endDate) {
const startPageDate = this.utils.setDate(new Date(startDate), 1);
const endPageDate = this.utils.setDate(new Date(endDate), 1);
const isInTheFuture = startPageDate < endPageDate;
if (this.isMinimumView) {
this.focus.delay = isInTheFuture ? this.slideDuration : 0;
} else {
this.focus.delay = 0;
}
this.setTransitionName(endDate - startDate);
},
/**
* Set the focus
* @param {Array} refs An array of `refs` to focus (in order of preference)
*/
setFocus(refs) {
this.focus.refs = refs;
this.applyFocus();
},
/**
* Determines which elements in datepicker should be focus-trapped
*/
setNavElements() {
if (!this.view)
return;
this.updateTabbableCell();
const view = this.ucFirst(this.view);
this.navElements = [
this.getInputField(),
this.getElementsFromSlot("beforeCalendarHeader"),
this.getElementsFromSlot(`beforeCalendarHeader${view}`),
this.getElementsFromHeader(),
this.tabbableCell,
this.getElementsFromSlot(`calendarFooter${view}`),
this.getElementsFromSlot("calendarFooter")
].filter((item) => !!item).reduce((acc, val) => acc.concat(val), []);
},
/**
* Keeps track of the currently focused index in the navElements array
*/
setNavElementsFocusedIndex() {
const activeElement = this.getActiveElement();
for (let i = 0; i < this.navElements.length; i += 1) {
if (activeElement === this.navElements[i]) {
this.navElementsFocusedIndex = i;
return;
}
}
this.navElementsFocusedIndex = 0;
},
/**
* Sets the focus-trapped cell in the picker
*/
// eslint-disable-next-line complexity
setTabbableCell() {
if (!this.$refs.picker || !this.$refs.picker.$refs.cells) {
return;
}
const pickerCells = this.$refs.picker.$refs.cells.$el;
this.tabbableCell = this.getActiveCell() || this.getTypedCell() || pickerCells.querySelector("button.selected:not(.muted):enabled") || pickerCells.querySelector("button.open:not(.muted):enabled") || pickerCells.querySelector("button.today:not(.muted):enabled") || pickerCells.querySelector("button.cell:not(.muted):enabled");
},
/**
* Sets the direction of the slide transition
* @param {Number} plusOrMinus Positive for the future; negative for the past
*/
setTransitionName(plusOrMinus) {
const isInTheFuture = plusOrMinus > 0;
if (this.isRtl) {
this.transitionName = isInTheFuture ? "slide-left" : "slide-right";
} else {
this.transitionName = isInTheFuture ? "slide-right" : "slide-left";
}
},
/**
* Focuses the first focusable element of an inline datepicker, so that the previous element on the page will be tabbed to
*/
tabAwayFromFirstElement() {
const firstElement = this.getFirstInlineFocusableElement();
this.setInlineTabbableCell();
firstElement.focus();
this.tabbableCell = this.inlineTabbableCell;
},
/**
* Focuses the last focusable element of an inline datepicker, so that the next element on the page will be tabbed to
*/
tabAwayFromLastElement() {
const lastElement = this.getLastInlineFocusableElement();
this.setInlineTabbableCell();
lastElement.focus();
this.tabbableCell = this.inlineTabbableCell;
},
/**
* Tab backwards through the focus-trapped elements
*/
tabBackwards() {
this.navElementsFocusedIndex -= 1;
if (this.navElementsFocusedIndex < 0) {
this.navElementsFocusedIndex = this.navElements.length - 1;
}
this.navElements[this.navElementsFocusedIndex].focus();
},
/**
* Tab forwards through the focus-trapped elements
*/
tabForwards() {
this.navElementsFocusedIndex += 1;
if (this.navElementsFocusedIndex >= this.navElements.length) {
this