@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
355 lines (354 loc) • 18.9 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { c as customElement } from "../../chunks/runtime.js";
import { ref } from "lit-html/directives/ref.js";
import { keyed } from "lit-html/directives/keyed.js";
import { html } from "lit";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { b as dateFromRange, i as inRange, s as sameDate, k as hasSameMonthAndYear, m as getFirstValidDateInMonth, n as nextMonth } from "../../chunks/date.js";
import { css } from "@lit/reactive-element/css-tag.js";
const CSS = {
calendar: "calendar",
calendarContainer: "calendar-container",
calendarStart: "calendar--start",
currentDay: "current-day",
dayContainer: "day-container",
insideRangeHover: "inside-range--hover",
month: "month",
noncurrent: "noncurrent",
outsideRangeHover: "outside-range--hover",
weekDays: "week-days",
weekHeader: "week-header",
weekHeaderContainer: "week-header-container"
};
const styles = css`:host([hidden]){display:none}[hidden]{display:none}.calendar-container{display:flex;inline-size:100%}:host([range][layout=vertical]) .calendar-container{flex-direction:column}.calendar{inline-size:100%}.week-header-container{display:flex;block-size:16px;padding-inline:var(--calcite-spacing-sm);padding-block:var(--calcite-spacing-md)}.week-header{display:flex;align-items:center;justify-content:center;text-align:center;font-size:var(--calcite-font-size--2);line-height:1rem;font-weight:var(--calcite-font-weight-bold);inline-size:14.2857142857%;color:var(--calcite-date-picker-week-header-text-color, var(--calcite-color-text-3))}.day-container{display:flex;inline-size:100%;min-inline-size:0px;justify-content:center}.day-container calcite-date-picker-day{inline-size:100%}.week-days{display:grid;grid-template-columns:repeat(7,1fr);grid-auto-rows:1fr;padding-inline:var(--calcite-spacing-sm);padding-block-end:var(--calcite-spacing-sm)}.month-header{display:flex;inline-size:100%;justify-content:space-between}.month{display:flex;inline-size:100%;flex-direction:column;justify-content:space-between}.day{font-size:var(--calcite-font-size)}:host([scale=s]) .week-days{padding-inline:var(--calcite-spacing-xs);padding-block-end:var(--calcite-spacing-xs)}:host([scale=s]) .week-header-container{padding-inline:var(--calcite-spacing-xs);padding-block:var(--calcite-spacing-sm)}:host([scale=s]) .day{font-size:var(--calcite-font-size-sm)}:host([scale=l]) .week-header{font-size:var(--calcite-font-size--1);line-height:1rem}:host([scale=l]) .week-days{padding-inline:var(--calcite-spacing-md);padding-block-end:var(--calcite-spacing-md)}:host([scale=l]) .week-header-container{padding-inline:var(--calcite-spacing-md);padding-block:var(--calcite-spacing-md-plus)}:host([scale=l]) .day{font-size:var(--calcite-font-size-md)}.calendar--start{border-width:0px;border-style:solid;border-color:var(--calcite-date-picker-range-calendar-divider-color, var(--calcite-color-border-1))}:host([range][layout=horizontal]) .calendar--start{border-inline-end-width:var(--calcite-border-width-sm)}:host([range][layout=vertical]) .calendar--start{border-block-end-width:var(--calcite-border-width-sm)}.noncurrent{pointer-events:none;opacity:0}`;
const DAYS_PER_WEEK = 7;
const DAYS_MAXIMUM_INDEX = 6;
const NUM_DAYS_TO_DISPLAY = 42;
class DatePickerMonth extends LitElement {
constructor() {
super();
this.activeDate = /* @__PURE__ */ new Date();
this.range = false;
this.calciteInternalDatePickerDayHover = createEvent({ cancelable: false });
this.calciteInternalDatePickerDaySelect = createEvent({ cancelable: false });
this.calciteInternalDatePickerMonthActiveDateChange = createEvent({ cancelable: false });
this.calciteInternalDatePickerMonthChange = createEvent({ cancelable: false });
this.calciteInternalDatePickerMonthMouseOut = createEvent({ cancelable: false });
this.listen("pointerout", this.pointerOutHandler);
this.listen("focusout", this.disableActiveFocus);
}
static {
this.properties = { focusedDate: [16, {}, { state: true }], activeDate: [0, {}, { attribute: false }], dateTimeFormat: [0, {}, { attribute: false }], endDate: [0, {}, { attribute: false }], headingLevel: [11, {}, { type: Number, reflect: true }], hoverRange: [0, {}, { attribute: false }], layout: [3, {}, { reflect: true }], localeData: [0, {}, { attribute: false }], max: [0, {}, { attribute: false }], messages: [0, {}, { attribute: false }], min: [0, {}, { attribute: false }], monthStyle: 1, range: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], selectedDate: [0, {}, { attribute: false }], startDate: [0, {}, { attribute: false }] };
}
static {
this.styles = styles;
}
load() {
this.focusedDate = this.selectedDate || this.activeDate;
}
willUpdate(changes) {
if (changes.has("activeDate")) {
this.updateFocusedDateWithActive(this.activeDate);
}
if (changes.has("selectedDate")) {
this.focusedDate = this.selectedDate;
}
}
updateFocusedDateWithActive(newActiveDate) {
if (!this.selectedDate) {
this.focusedDate = inRange(newActiveDate, this.min, this.max) ? newActiveDate : dateFromRange(newActiveDate, this.min, this.max);
}
}
keyDownHandler(event) {
if (event.defaultPrevented) {
return;
}
const isRTL = this.el.dir === "rtl";
const dateValue = event.target.value;
switch (event.key) {
case "ArrowUp":
event.preventDefault();
this.addDays(-7, dateValue);
break;
case "ArrowRight":
event.preventDefault();
this.addDays(isRTL ? -1 : 1, dateValue);
break;
case "ArrowDown":
event.preventDefault();
this.addDays(7, dateValue);
break;
case "ArrowLeft":
event.preventDefault();
this.addDays(isRTL ? 1 : -1, dateValue);
break;
case "PageUp":
event.preventDefault();
this.addMonths(-1, dateValue);
break;
case "PageDown":
event.preventDefault();
this.addMonths(1, dateValue);
break;
case "Home":
event.preventDefault();
this.activeDate.setDate(1);
this.addDays(0, dateValue);
break;
case "End":
event.preventDefault();
this.activeDate.setDate(new Date(this.activeDate.getFullYear(), this.activeDate.getMonth() + 1, 0).getDate());
this.addDays(0, dateValue);
break;
case "Enter":
case " ":
event.preventDefault();
break;
case "Tab":
this.activeFocus = false;
}
}
disableActiveFocus() {
this.activeFocus = false;
}
pointerOutHandler() {
this.calciteInternalDatePickerMonthMouseOut.emit();
}
addMonths(step, targetDate) {
const nextDate = new Date(targetDate);
nextDate.setMonth(targetDate.getMonth() + step);
this.calciteInternalDatePickerMonthActiveDateChange.emit(dateFromRange(nextDate, this.min, this.max));
this.focusedDate = dateFromRange(nextDate, this.min, this.max);
this.activeFocus = true;
this.calciteInternalDatePickerDayHover.emit(nextDate);
}
addDays(step = 0, targetDate) {
const nextDate = new Date(targetDate);
nextDate.setDate(targetDate.getDate() + step);
this.calciteInternalDatePickerMonthActiveDateChange.emit(dateFromRange(nextDate, this.min, this.max));
this.focusedDate = dateFromRange(nextDate, this.min, this.max);
this.activeFocus = true;
this.calciteInternalDatePickerDayHover.emit(nextDate);
}
getPreviousMonthDays(month, year, startOfWeek) {
const lastDate = new Date(year, month, 0);
const date = lastDate.getDate();
const startDay = lastDate.getDay();
const days = [];
if (startDay === (startOfWeek + DAYS_MAXIMUM_INDEX) % DAYS_PER_WEEK) {
return days;
}
if (startDay === startOfWeek) {
return [date];
}
for (let i = (DAYS_PER_WEEK + startDay - startOfWeek) % DAYS_PER_WEEK; i >= 0; i--) {
days.push(date - i);
}
return days;
}
getCurrentMonthDays(month, year) {
const num = new Date(year, month + 1, 0).getDate();
const days = [];
for (let i = 0; i < num; i++) {
days.push(i + 1);
}
return days;
}
getNextMonthDays(month, year, startOfWeek) {
const endDay = new Date(year, month + 1, 0).getDay();
const days = [];
if (endDay === (startOfWeek + DAYS_MAXIMUM_INDEX) % DAYS_PER_WEEK) {
return days;
}
for (let i = 0; i < (DAYS_MAXIMUM_INDEX - (endDay - startOfWeek)) % DAYS_PER_WEEK; i++) {
days.push(i + 1);
}
return days;
}
betweenSelectedRange(date) {
return !!(this.startDate && this.endDate && date > this.startDate && date < this.endDate && !this.isRangeHover(date));
}
isSelected(date) {
return !!(sameDate(date, this.selectedDate) || this.startDate && sameDate(date, this.startDate) || this.endDate && sameDate(date, this.endDate));
}
isStartOfRange(date) {
return !!(this.startDate && !sameDate(this.startDate, this.endDate) && sameDate(this.startDate, date) && !this.isEndOfRange(date));
}
isEndOfRange(date) {
return !!(this.endDate && !sameDate(this.startDate, this.endDate) && sameDate(this.endDate, date) || !this.endDate && this.hoverRange && sameDate(this.startDate, this.hoverRange.end) && sameDate(date, this.hoverRange.end));
}
dayHover(event) {
const target = event.target;
if (target.disabled) {
this.calciteInternalDatePickerMonthMouseOut.emit();
} else {
this.calciteInternalDatePickerDayHover.emit(target.value);
}
event.stopPropagation();
}
daySelect(event) {
const target = event.target;
this.activeFocus = false;
this.calciteInternalDatePickerDaySelect.emit(target.value);
event.stopPropagation();
}
isFocusedOnStart() {
return this.hoverRange?.focused === "start";
}
isHoverInRange() {
if (!this.hoverRange || !this.startDate) {
return false;
}
const { start, end } = this.hoverRange;
const isStartFocused = this.isFocusedOnStart();
const isEndAfterStart = this.startDate && end > this.startDate;
const isEndBeforeEnd = this.endDate && end < this.endDate;
const isStartAfterStart = this.startDate && start > this.startDate;
const isStartBeforeEnd = this.endDate && start < this.endDate;
const isEndDateAfterStartAndBeforeEnd = !isStartFocused && this.startDate && isEndAfterStart && (!this.endDate || isEndBeforeEnd);
const isStartDateBeforeEndAndAfterStart = isStartFocused && this.startDate && isStartAfterStart && isStartBeforeEnd;
return isEndDateAfterStartAndBeforeEnd || isStartDateBeforeEndAndAfterStart;
}
isRangeHover(date) {
if (!this.hoverRange) {
return false;
}
const { start, end } = this.hoverRange;
const isStartFocused = this.isFocusedOnStart();
const insideRange = this.isHoverInRange();
const isDateBeforeStartDateAndAfterStart = date > start && date < this.startDate;
const isDateAfterEndDateAndBeforeEnd = date < end && date > this.endDate;
const isDateBeforeEndDateAndAfterEnd = date > end && date < this.endDate;
const isDateAfterStartDateAndBeforeStart = date < start && date > this.startDate;
const isDateAfterStartDateAndBeforeEnd = date < end && date > this.startDate;
const isDateBeforeEndDateAndAfterStart = date > start && date < this.endDate;
const hasBothStartAndEndDate = this.startDate && this.endDate;
if (insideRange) {
if (hasBothStartAndEndDate) {
return isStartFocused ? date < this.endDate && (isDateAfterStartDateAndBeforeStart || isDateBeforeStartDateAndAfterStart) : isDateBeforeEndDateAndAfterEnd || isDateAfterEndDateAndBeforeEnd;
} else if (this.startDate && !this.endDate) {
return isStartFocused ? isDateBeforeStartDateAndAfterStart : isDateAfterStartDateAndBeforeEnd;
} else if (!this.startDate && this.endDate) {
return isStartFocused ? isDateBeforeEndDateAndAfterStart : isDateAfterEndDateAndBeforeEnd;
}
} else {
if (hasBothStartAndEndDate) {
return isStartFocused ? isDateBeforeStartDateAndAfterStart : isDateAfterEndDateAndBeforeEnd;
}
}
}
getDays(prevMonthDays, currMonthDays, nextMonthDays, position = "start") {
let month = this.activeDate.getMonth();
const nextMonth2 = month + 1;
month = position === "end" ? nextMonth2 : month;
let dayInWeek = 0;
const getDayInWeek = () => dayInWeek++ % 7;
const year = this.activeDate.getFullYear();
const days = [
...prevMonthDays.map((day) => {
return {
active: false,
day,
dayInWeek: getDayInWeek(),
date: new Date(year, month - 1, day)
};
}),
...currMonthDays.map((day) => {
const date = new Date(year, month, day);
const isCurrentDay = sameDate(date, /* @__PURE__ */ new Date());
const active = this.focusedDate && this.focusedDate !== this.startDate && this.focusedDate !== this.endDate ? sameDate(date, this.focusedDate) : sameDate(date, this.startDate) || sameDate(date, this.endDate);
return {
active,
currentMonth: true,
currentDay: isCurrentDay,
day,
dayInWeek: getDayInWeek(),
date,
ref: true
};
}),
...nextMonthDays.map((day) => {
return {
active: false,
day,
dayInWeek: getDayInWeek(),
date: new Date(year, nextMonth2, day)
};
})
];
return days;
}
monthHeaderSelectChange(event) {
const date = new Date(event.detail);
const target = event.target;
this.updateFocusableDate(date);
event.stopPropagation();
this.calciteInternalDatePickerMonthChange.emit({ date, position: target.position });
}
updateFocusableDate(date) {
if (!this.selectedDate || !this.range) {
this.focusedDate = this.getFirstValidDateOfMonth(date);
} else if (this.selectedDate && this.range) {
if (!hasSameMonthAndYear(this.startDate, date) || !hasSameMonthAndYear(this.endDate, date)) {
this.focusedDate = this.getFirstValidDateOfMonth(date);
}
}
}
getFirstValidDateOfMonth(date) {
return date.getDate() === 1 ? date : getFirstValidDateInMonth(date, this.min, this.max);
}
render() {
const month = this.activeDate.getMonth();
const year = this.activeDate.getFullYear();
const startOfWeek = this.localeData.weekStart % 7;
const { abbreviated, short, narrow } = this.localeData.days;
const weekDays = this.scale === "s" ? narrow || short || abbreviated : short || abbreviated || narrow;
const adjustedWeekDays = [...weekDays.slice(startOfWeek, 7), ...weekDays.slice(0, startOfWeek)];
const curMonDays = this.getCurrentMonthDays(month, year);
const prevMonDays = this.getPreviousMonthDays(month, year, startOfWeek);
const nextMonDays = this.getNextMonthDays(month, year, startOfWeek);
const numDaysDisplayed = curMonDays.length + prevMonDays.length + nextMonDays.length;
if (numDaysDisplayed < NUM_DAYS_TO_DISPLAY) {
const initialDay = nextMonDays.length ? nextMonDays[nextMonDays.length - 1] : 0;
for (let i = 1; i <= NUM_DAYS_TO_DISPLAY - numDaysDisplayed; i++) {
nextMonDays.push(initialDay + i);
}
}
const nextMonth2 = month + 1;
const endCalendarPrevMonDays = this.getPreviousMonthDays(nextMonth2, year, startOfWeek);
const endCalendarCurrMonDays = this.getCurrentMonthDays(nextMonth2, year);
const endCalendarNextMonDays = this.getNextMonthDays(nextMonth2, year, startOfWeek);
const days = this.getDays(prevMonDays, curMonDays, nextMonDays);
const nextMonthDays = this.getDays(endCalendarPrevMonDays, endCalendarCurrMonDays, endCalendarNextMonDays, "end");
return html`<div class=${safeClassMap({ [CSS.calendarContainer]: true })} role=grid>${this.renderCalendar(adjustedWeekDays, days)}${this.range && this.renderCalendar(adjustedWeekDays, nextMonthDays, true) || ""}</div>`;
}
renderDateDay({ active, currentMonth, currentDay, date, day, dayInWeek, ref: ref$1 }, key) {
const isDateInRange = inRange(date, this.min, this.max);
return keyed(key, html`<div class=${safeClassMap({ [CSS.dayContainer]: true })} role=gridcell><calcite-date-picker-day .active=${active} class=${safeClassMap({
[CSS.currentDay]: currentDay,
[CSS.insideRangeHover]: this.isHoverInRange(),
[CSS.outsideRangeHover]: !this.isHoverInRange(),
[CSS.noncurrent]: this.range && !currentMonth
})} .currentMonth=${currentMonth} .dateTimeFormat=${this.dateTimeFormat} .day=${day} .disabled=${!isDateInRange} .endOfRange=${this.isEndOfRange(date)} .highlighted=${this.betweenSelectedRange(date)} =${this.dayHover} =${this.daySelect} .range=${!!this.startDate && !!this.endDate && !sameDate(this.startDate, this.endDate)} .rangeEdge=${dayInWeek === 0 ? "start" : dayInWeek === 6 ? "end" : void 0} .rangeHover=${isDateInRange && this.isRangeHover(date)} .scale=${this.scale} .selected=${this.isSelected(date)} .startOfRange=${this.isStartOfRange(date)} .value=${date} ${ref((el) => {
if (ref$1 && active && this.activeFocus) {
el?.setFocus();
}
})}></calcite-date-picker-day></div>`);
}
renderCalendar(weekDays, days, isEndCalendar = false) {
return html`<div class=${safeClassMap({
[CSS.calendar]: true,
[CSS.calendarStart]: !isEndCalendar
})}><calcite-date-picker-month-header .activeDate=${isEndCalendar ? nextMonth(this.activeDate) : this.activeDate} data-test-calendar=${isEndCalendar ? "end" : "start"} .headingLevel=${this.headingLevel} .localeData=${this.localeData} .max=${this.max} .messages=${this.messages} .min=${this.min} .monthStyle=${this.monthStyle} =${this.monthHeaderSelectChange} .position=${isEndCalendar ? "end" : this.range ? "start" : null} .scale=${this.scale} .selectedDate=${this.selectedDate}></calcite-date-picker-month-header>${this.renderMonthCalendar(weekDays, days, isEndCalendar)}</div>`;
}
renderMonthCalendar(weekDays, days, isEndCalendar = false) {
const endCalendarStartIndex = 50;
return html`<div class=${safeClassMap({ [CSS.month]: true })} =${this.keyDownHandler}><div class=${safeClassMap({ [CSS.weekHeaderContainer]: true })} role=row>${weekDays.map((weekday) => html`<span class=${safeClassMap({ [CSS.weekHeader]: true })} role=columnheader>${weekday}</span>`)}</div><div class=${safeClassMap({ [CSS.weekDays]: true })} role=row>${days.map((day, index) => this.renderDateDay(day, isEndCalendar ? endCalendarStartIndex + index : index))}</div></div>`;
}
}
customElement("calcite-date-picker-month", DatePickerMonth);
export {
DatePickerMonth
};