@ebay/ebayui-core
Version:
Collection of core eBay components; considered to be the building blocks for all composite structures, pages & apps.
299 lines (298 loc) • 12.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const date_utils_1 = require("../../common/dates/date-utils");
const dates_1 = require("../../common/dates");
const DAY_UPDATE_KEYMAP = {
ArrowRight: 1,
ArrowLeft: -1,
ArrowDown: 7,
ArrowUp: -7,
};
class Calendar {
onCreate(input) {
this.locale = input.locale;
const { firstDayOfWeek, weekdayLabels } = (0, date_utils_1.getWeekdayInfo)(input.locale);
const todayISO = (0, date_utils_1.toLocalISO)(new Date());
this.state = {
focusISO: null,
baseISO: todayISO,
tabindexISO: todayISO,
todayISO,
offset: 0,
firstDayOfWeek,
weekdayLabels,
rangeStart: null,
rangeEnd: null,
disableBefore: null,
disableAfter: null,
disableWeekdays: [],
disableList: [],
};
}
onInput(input) {
var _a, _b, _c, _d, _e;
if (input.locale !== this.locale) {
this.locale = input.locale;
const { firstDayOfWeek, weekdayLabels } = (0, date_utils_1.getWeekdayInfo)(input.locale);
this.state.firstDayOfWeek = firstDayOfWeek;
this.state.weekdayLabels = weekdayLabels;
}
if (input.todayISO) {
const newTodayISO = (0, date_utils_1.toISO)(new Date(input.todayISO));
this.state.todayISO = newTodayISO;
this.state.baseISO = newTodayISO;
this.state.tabindexISO = newTodayISO;
}
if (input.selected) {
// If no selected times are visible, snap the view to the first one
const selectedISOs = Array.isArray(input.selected)
? input.selected
: [input.selected];
const currFirstISO = this.getFirstVisibleISO();
const currLastISO = this.getLastVisibleISO(input);
const selectedTimeInView = selectedISOs.find((time) => time >= currFirstISO && time <= currLastISO);
if (selectedTimeInView === undefined) {
this.state.baseISO = this.state.tabindexISO = selectedISOs[0];
this.state.offset = 0;
}
}
this.calculateRangeDisplay(input);
// handle changes regarding disabled dates
this.state.disableBefore = (0, date_utils_1.dateArgToISO)(input.disableBefore);
this.state.disableAfter = (0, date_utils_1.dateArgToISO)(input.disableAfter);
this.state.disableWeekdays = (_a = input.disableWeekdays) !== null && _a !== void 0 ? _a : [];
this.state.disableList = (_c = (_b = input.disableList) === null || _b === void 0 ? void 0 : _b.map(date_utils_1.dateArgToISO)) !== null && _c !== void 0 ? _c : [];
if (this.isDisabled(this.state.tabindexISO)) {
// The current tabindex is disabled, so we have to find a new one
const firstActive = this.getFirstActiveISO(input);
if (firstActive) {
// This month has active days, so we can use the first one
this.state.tabindexISO = firstActive;
}
else if (this.state.disableBefore &&
this.state.tabindexISO < this.state.disableBefore) {
// This month has no active days and the start of _possible_ dates is in the future, so we change months to the start of possible dates
this.state.baseISO = this.state.disableBefore;
this.state.offset = 0;
this.state.tabindexISO =
(_d = this.getFirstActiveISO(input)) !== null && _d !== void 0 ? _d : this.state.disableBefore;
}
else if (this.state.disableAfter &&
this.state.tabindexISO > this.state.disableAfter) {
// This month has no active days and the end of _possible_ dates is in the past, so we change months to the end of possible dates
this.state.baseISO = this.state.disableAfter;
this.state.offset = 0;
this.state.tabindexISO =
(_e = this.getFirstActiveISO(input)) !== null && _e !== void 0 ? _e : this.state.disableAfter;
}
else {
// This may be reached in very specific edge cases, such as when the user has disabled all days in the current month manually
// In this case, we leave the tabindex and position as is. This is a fall-through case.
}
}
while (this.state.disableAfter &&
this.getMonthDate(this.state.offset + (input.numMonths || 1) - 1).toISOString() > this.state.disableAfter) {
this.state.offset--;
}
}
isDisabled(iso) {
return ((this.state.disableBefore && iso < this.state.disableBefore) ||
(this.state.disableAfter && iso > this.state.disableAfter) ||
this.state.disableWeekdays.includes((0, date_utils_1.fromISO)(iso).getUTCDay()) ||
this.state.disableList.includes(iso));
}
onDaySelect(day) {
this.emit("select", { iso: day });
}
onDayFocus(day) {
this.state.focusISO = this.state.tabindexISO = day;
this.calculateRangeDisplay();
}
onDayBlur() {
this.state.focusISO = null;
this.calculateRangeDisplay();
}
onKeyDown(event) {
const dayChange = DAY_UPDATE_KEYMAP[event.key];
if (dayChange) {
event.preventDefault();
// find new tabindex iso, skipping up to 7 disabled cells
let tries = 7;
let iso = this.state.tabindexISO;
do {
iso = (0, date_utils_1.offsetISO)(iso, dayChange);
} while (tries-- > 0 && this.isDisabled(iso));
if (tries > 0) {
// check for edges of calendar
const firstVisibleISO = this.getFirstVisibleISO();
const lastVisibleISO = this.getLastVisibleISO();
if (iso < firstVisibleISO) {
if (this.input.navigable)
this.prevMonth();
else
iso = firstVisibleISO;
}
else if (iso > lastVisibleISO) {
if (this.input.navigable)
this.nextMonth();
else
iso = lastVisibleISO;
}
this.setTabindexAndFocus(iso);
this.emit("focus", { iso: this.state.tabindexISO });
}
}
else {
switch (event.key) {
case "PageUp":
this.prevMonth(true);
break;
case "PageDown":
this.nextMonth(true);
break;
case "Home": {
const firstActiveISO = this.getFirstActiveISO();
if (firstActiveISO) {
this.setTabindexAndFocus(firstActiveISO);
this.emit("focus", { iso: this.state.tabindexISO });
}
break;
}
case "End": {
const lastActiveISO = this.getLastActiveISO();
if (lastActiveISO) {
this.setTabindexAndFocus(lastActiveISO);
this.emit("focus", { iso: this.state.tabindexISO });
}
break;
}
default:
}
}
}
getMonthDate(offset) {
const baseDate = (0, date_utils_1.fromISO)(this.state.baseISO);
const date = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth() + offset));
return date;
}
getFirstVisibleISO() {
return (0, date_utils_1.toISO)(this.getMonthDate(this.state.offset));
}
getLastVisibleISO(input = this.input) {
const baseDate = (0, date_utils_1.fromISO)(this.state.baseISO);
return (0, date_utils_1.toISO)(new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth() +
this.state.offset +
(input.numMonths || 1), 0)));
}
getFirstActiveISO(input = this.input) {
let iso = this.getFirstVisibleISO();
const lastVisible = this.getLastVisibleISO(input);
while (iso <= lastVisible && this.isDisabled(iso)) {
iso = (0, date_utils_1.offsetISO)(iso, 1);
}
return iso > lastVisible ? null : iso;
}
getLastActiveISO(input = this.input) {
let iso = this.getLastVisibleISO(input);
const firstVisible = this.getFirstVisibleISO();
while (iso >= firstVisible && this.isDisabled(iso)) {
iso = (0, date_utils_1.offsetISO)(iso, -1);
}
return iso < firstVisible ? null : iso;
}
monthTitle(date) {
const formatter = new Intl.DateTimeFormat((0, dates_1.localeDefault)(this.input.locale), {
month: "long",
year: "numeric",
});
return formatter.format(new Date(date.getUTCFullYear(), date.getUTCMonth()));
}
prevMonth(focus) {
if (this.state.disableBefore &&
this.getFirstVisibleISO() <= this.state.disableBefore) {
return false;
}
this.state.offset--;
let newTabindexISO = this.state.tabindexISO;
const lastActiveISO = this.getLastActiveISO();
if (lastActiveISO && this.state.tabindexISO > lastActiveISO) {
newTabindexISO = this.state.tabindexISO = lastActiveISO;
}
if (focus) {
this.setTabindexAndFocus(newTabindexISO);
}
this.emit("month-change", {
iso: (0, date_utils_1.toISO)(this.getMonthDate(this.state.offset)),
});
return true;
}
nextMonth(focus) {
if (this.state.disableAfter &&
this.getLastVisibleISO() >= this.state.disableAfter) {
return false;
}
this.state.offset++;
let newTabindexISO = this.state.tabindexISO;
const firstActiveISO = this.getFirstActiveISO();
if (firstActiveISO && this.state.tabindexISO < firstActiveISO) {
newTabindexISO = this.state.tabindexISO = firstActiveISO;
}
if (focus) {
this.setTabindexAndFocus(newTabindexISO);
}
this.emit("month-change", {
iso: (0, date_utils_1.toISO)(this.getMonthDate(this.state.offset + (this.input.numMonths || 1))),
});
return true;
}
setTabindexAndFocus(iso) {
this.state.tabindexISO = iso;
// After UI updates, focus on the new tabindex date
setTimeout(() => { var _a; return (_a = this.getEl(iso)) === null || _a === void 0 ? void 0 : _a.focus(); });
}
calculateRangeDisplay(input = this.input) {
if (input.selected && input.range) {
// Determine range display (state.rangeStart-state.rangeEnd)
let iso1, iso2;
if (Array.isArray(input.selected)) {
// Two elements are selected, we can use them as the ends of the range.
[iso1, iso2] = input.selected;
}
else if (this.state.focusISO) {
// One element is selected and the user is focused on a date,
// so we use the selected component and the focus date instead
iso1 = input.selected;
iso2 = this.state.focusISO;
}
if (iso1 && iso2) {
// Both ends of the range are valid, figure out which is first
if (iso1 < iso2) {
this.state.rangeStart = iso1;
this.state.rangeEnd = iso2;
}
else {
this.state.rangeStart = iso2;
this.state.rangeEnd = iso1;
}
}
else {
// We can't display a range, so ensure that no range is highlighted
this.state.rangeStart = this.state.rangeEnd = null;
}
}
else {
// We can't display a range, so ensure that no range is highlighted
this.state.rangeStart = this.state.rangeEnd = null;
}
}
isInRange(iso) {
if (!this.state.rangeStart || !this.state.rangeEnd)
// range doesn't exist
return false;
if (iso < this.state.rangeStart || iso > this.state.rangeEnd)
// date is outside of range
return false;
return true;
}
}
module.exports = Calendar;