@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
278 lines (277 loc) • 18.3 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 { live } from "lit-html/directives/live.js";
import { e as calciteSpacingXxs, f as calciteSpacingBase, g as calciteSpacingSm } from "../../chunks/global.js";
import { html, nothing } from "lit";
import { createRef, ref } from "lit-html/directives/ref.js";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { b as dateFromRange, n as nextMonth, p as prevMonth, g as formatCalendarYear, h as parseCalendarYear, j as getDateInMonth, i as inRange, k as hasSameMonthAndYear, l as getOrder } from "../../chunks/date.js";
import { l as closestElementCrossShadowBoundary, A as getTextWidth } from "../../chunks/dom.js";
import { i as isActivationKey } from "../../chunks/key.js";
import { n as numberStringFormatter } from "../../chunks/locale.js";
import { css } from "@lit/reactive-element/css-tag.js";
const styles = css`:host{display:block}.header{display:flex;block-size:100%;align-items:center;justify-content:space-between}.chevron-container{display:flex;align-items:center}:host([scale=s]){block-size:24px;margin:var(--calcite-spacing-xs);margin-inline-start:var(--calcite-spacing-sm)}:host([scale=s]) .chevron-container,:host([scale=s]) .chevron{min-inline-size:24px;block-size:24px}:host([scale=m]){block-size:32px;margin:var(--calcite-spacing-sm);margin-inline-start:var(--calcite-spacing-sm-plus)}:host([scale=m]) .chevron-container,:host([scale=m]) .chevron{min-inline-size:32px;block-size:32px;--calcite-internal-action-padding-block: var(--calcite-spacing-xxs)}:host([scale=l]){block-size:44px;margin:var(--calcite-spacing-xs);margin-inline-start:var(--calcite-spacing-sm)}:host([scale=l]) .chevron-container,:host([scale=l]) .chevron{min-inline-size:44px;block-size:44px;--calcite-internal-action-padding-block: var(--calcite-spacing-sm-plus)}.chevron{box-sizing:content-box;display:flex;block-size:100%;inline-size:100%;flex-grow:0;cursor:pointer;align-items:center;justify-content:center;border-style:none;outline-color:transparent;transition-property:background-color,block-size,border-color,box-shadow,color,inset-block-end,inset-block-start,inset-inline-end,inset-inline-start,inset-size,opacity,outline-color,transform;transition-duration:var(--calcite-animation-timing);transition-timing-function:ease-in-out;--calcite-internal-action-padding-block: 0;--calcite-action-background-color: var(--calcite-date-picker-header-action-background-color);--calcite-action-background-color-hover: var(--calcite-date-picker-header-action-background-color-hover);--calcite-action-background-color-press: var(--calcite-date-picker-header-action-background-color-press);--calcite-action-text-color: var(--calcite-date-picker-header-action-text-color);--calcite-action-text-color-press: var(--calcite-date-picker-header-action-text-color-press)}.chevron:focus{outline:2px solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(-2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}.chevron[aria-disabled=true]{pointer-events:none}.month-year-container{display:flex;block-size:100%;inline-size:100%;flex:1 1 auto;align-items:center;justify-content:flex-start;text-align:center;line-height:1;gap:var(--calcite-spacing-xxs)}.month-year-container.range-calendar{justify-content:center}.year-container{position:relative;display:flex;block-size:100%}.suffix{display:flex;align-items:center}.year,.suffix{margin-inline:var(--calcite-spacing-xxs);font-weight:var(--calcite-font-weight-medium);color:var(--calcite-date-picker-year-text-color, var(--calcite-color-text-1));font-size:var(--calcite-font-size-md);line-height:var(--calcite-font-line-height-fixed-lg)}.year{position:relative;display:inline-block;border-style:none;background-color:transparent;text-align:center;font-family:inherit;outline-color:transparent;inline-size:44px}.year:hover{transition-duration:.1s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:outline-color;outline:2px solid var(--calcite-color-border-2);outline-offset:-2px}.year:focus{outline:2px solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(-2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}.month-select{--calcite-select-internal-border-width: 0;--calcite-select-internal-icon-border-inline-end-width: 0;--calcite-select-spacing-inline: var(--calcite-spacing-xxs);--calcite-select-font-size: var(--calcite-date-picker-month-select-font-size, var(--calcite-font-size-md));--calcite-select-text-color: var(--calcite-date-picker-month-select-text-color, var(--calcite-color-text-1));--calcite-select-icon-color: var(--calcite-date-picker-month-select-icon-color);--calcite-select-icon-color-hover: var(--calcite-date-picker-month-select-icon-color-hover);--calcite-internal-select-spacing-block: var(--calcite-spacing-xxs);--calcite-internal-select-icon-container-padding-inline: var(--calcite-spacing-xxs);--calcite-internal-select-line-height: var(--calcite-font-line-height-fixed-lg);--calcite-internal-select-font-weight: var(--calcite-font-weight-medium)}:host([scale=s]) .month-year-container .month-select{--calcite-select-spacing-inline: var(--calcite-spacing-base);--calcite-select-font-size: var(--calcite-date-picker-month-select-font-size, var(--calcite-font-size));--calcite-internal-select-spacing-block: var(--calcite-spacing-base);--calcite-internal-select-icon-container-padding-inline: var(--calcite-spacing-base);--calcite-internal-select-block-size: 24px;--calcite-internal-select-line-height: var(--calcite-font-line-height-fixed-base)}:host([scale=s]) .month-year-container .year{inline-size:40px}:host([scale=s]) .month-year-container .year,:host([scale=s]) .month-year-container .suffix{font-size:var(--calcite-font-size);line-height:var(--calcite-font-line-height-fixed-base)}:host([scale=l]) .month-year-container .month-select{--calcite-select-spacing-inline: var(--calcite-spacing-sm);--calcite-select-font-size: var(--calcite-date-picker-month-select-font-size, var(--calcite-font-size-lg));--calcite-internal-select-spacing-block: var(--calcite-spacing-sm);--calcite-internal-select-icon-container-padding-inline: var(--calcite-spacing-sm);--calcite-internal-select-block-size: 44px;--calcite-internal-select-line-height: var(--calcite-font-line-height-fixed-xl)}:host([scale=l]) .month-year-container .year{inline-size:48px}:host([scale=l]) .month-year-container .year,:host([scale=l]) .month-year-container .suffix{font-size:var(--calcite-font-size-lg);line-height:var(--calcite-font-line-height-fixed-xl)}:host([hidden]){display:none}[hidden]{display:none}`;
const CSS = {
header: "header",
chevron: "chevron",
chevronContainer: "chevron-container",
monthYearContainer: "month-year-container",
monthPicker: "month-select",
rangeCalendar: "range-calendar",
suffix: "suffix",
yearContainer: "year-container"
};
const ICON = {
chevronLeft: "chevron-left",
chevronRight: "chevron-right"
};
const ICON_WIDTH_M = 16;
class DatePickerMonthHeader extends LitElement {
constructor() {
super(...arguments);
this.monthPickerEl = createRef();
this.yearInputEl = createRef();
this.calciteInternalDatePickerMonthHeaderSelectChange = createEvent({ cancelable: false });
}
static {
this.properties = { nextMonthDate: [16, {}, { state: true }], prevMonthDate: [16, {}, { state: true }], activeDate: [0, {}, { attribute: false }], headingLevel: [9, {}, { type: Number }], localeData: [0, {}, { attribute: false }], max: [0, {}, { attribute: false }], messages: [0, {}, { attribute: false }], min: [0, {}, { attribute: false }], monthStyle: 1, position: 1, scale: [3, {}, { reflect: true }], selectedDate: [0, {}, { attribute: false }] };
}
static {
this.styles = styles;
}
connectedCallback() {
super.connectedCallback();
this.setNextPrevMonthDates();
}
load() {
this.parentDatePickerEl = closestElementCrossShadowBoundary(this.el, "calcite-date-picker");
}
willUpdate(changes) {
if (this.hasUpdated && (changes.has("activeDate") || changes.has("localeData"))) {
this.setYearSelectMenuWidth();
}
if (this.hasUpdated && changes.has("scale")) {
this.setYearSelectWidthOffset();
}
if (changes.has("min") || changes.has("max") || changes.has("activeDate")) {
this.setNextPrevMonthDates();
}
}
loaded() {
this.setYearSelectWidthOffset();
}
setNextPrevMonthDates() {
if (!this.activeDate) {
return;
}
this.nextMonthDate = dateFromRange(nextMonth(this.activeDate), this.min, this.max);
this.prevMonthDate = dateFromRange(prevMonth(this.activeDate), this.min, this.max);
}
onYearKey(event) {
const localizedYear = this.parseCalendarYear(event.target.value);
switch (event.key) {
case "ArrowDown":
event.preventDefault();
this.setYear({ localizedYear, offset: -1 });
break;
case "ArrowUp":
event.preventDefault();
this.setYear({ localizedYear, offset: 1 });
break;
}
}
formatCalendarYear(year) {
return numberStringFormatter.localize(`${formatCalendarYear(year, this.localeData)}`);
}
parseCalendarYear(year) {
return numberStringFormatter.localize(`${parseCalendarYear(Number(numberStringFormatter.delocalize(year)), this.localeData)}`);
}
onYearChange(event) {
this.setYear({
localizedYear: this.parseCalendarYear(event.target.value)
});
}
onYearInput(event) {
this.setYear({
localizedYear: this.parseCalendarYear(event.target.value),
commit: false
});
}
prevMonthClick(event) {
this.handleArrowClick(event, this.prevMonthDate);
}
prevMonthKeydown(event) {
if (isActivationKey(event.key)) {
this.prevMonthClick(event);
}
}
nextMonthClick(event) {
this.handleArrowClick(event, this.nextMonthDate);
}
nextMonthKeydown(event) {
if (isActivationKey(event.key)) {
this.nextMonthClick(event);
}
}
async handleArrowClick(event, date) {
event.preventDefault();
await this.handlePenultimateValidMonth(event);
this.calciteInternalDatePickerMonthHeaderSelectChange.emit(date);
}
handleMonthChange(event) {
const target = event.target;
const { abbreviated, wide } = this.localeData.months;
const localeMonths = this.monthStyle === "wide" ? wide : abbreviated;
const monthIndex = localeMonths.indexOf(target.value);
let newDate = getDateInMonth(this.activeDate, monthIndex);
if (!inRange(newDate, this.min, this.max)) {
newDate = dateFromRange(newDate, this.min, this.max);
}
this.calciteInternalDatePickerMonthHeaderSelectChange.emit(newDate);
this.setYearSelectMenuWidth();
}
getInRangeDate({ localizedYear, offset = 0 }) {
const { min, max, activeDate } = this;
const parsedYear = Number(numberStringFormatter.delocalize(localizedYear));
const length = parsedYear.toString().length;
const year = isNaN(parsedYear) ? false : parsedYear + offset;
const inRange2 = year && (!min || min.getFullYear() <= year) && (!max || max.getFullYear() >= year);
if (year && inRange2 && length === localizedYear.length) {
const nextDate = new Date(activeDate);
nextDate.setFullYear(year);
return dateFromRange(nextDate, min, max);
}
}
setYear({ localizedYear, commit = true, offset = 0 }) {
const { yearInputEl: { value: yearInputEl }, activeDate } = this;
const inRangeDate = this.getInRangeDate({ localizedYear, offset });
if (inRangeDate) {
this.calciteInternalDatePickerMonthHeaderSelectChange.emit(inRangeDate);
}
if (commit) {
yearInputEl.value = this.formatCalendarYear((inRangeDate || activeDate).getFullYear());
}
}
setYearSelectWidthOffset() {
this.yearSelectWidthOffset = ICON_WIDTH_M + 3 * parseInt(this.getYearSelectPadding());
this.setYearSelectMenuWidth();
}
setYearSelectMenuWidth() {
const el = this.monthPickerEl.value;
if (!el) {
return;
}
requestAnimationFrame(() => {
const computedStyle = getComputedStyle(el);
const shorthandFont = `${computedStyle.fontStyle} ${computedStyle.fontVariant} ${computedStyle.fontWeight} ${computedStyle.fontSize}/${computedStyle.lineHeight} ${computedStyle.fontFamily}`;
const localeMonths = this.localeData.months[this.monthStyle];
const activeLocaleMonth = localeMonths[this.activeDate.getMonth()];
const selectedOptionWidth = Math.ceil(getTextWidth(activeLocaleMonth, shorthandFont));
el.style.width = `${selectedOptionWidth + this.yearSelectWidthOffset}px`;
});
}
isMonthInRange(index) {
const newActiveDate = getDateInMonth(this.activeDate, index);
if (!this.min && !this.max || inRange(newActiveDate, this.min, this.max)) {
return true;
}
return hasSameMonthAndYear(newActiveDate, this.max) || hasSameMonthAndYear(newActiveDate, this.min);
}
async handlePenultimateValidMonth(event) {
const target = event.target;
const direction = target.getAttribute("data-direction");
const isDirectionLeft = direction === "left";
let isTargetLastValidMonth;
if (isDirectionLeft && this.min) {
const prevMonthDate = dateFromRange(prevMonth(this.activeDate), this.min, this.max);
isTargetLastValidMonth = hasSameMonthAndYear(prevMonthDate, this.min);
} else if (this.max) {
const nextMonthDate = dateFromRange(nextMonth(this.activeDate), this.min, this.max);
isTargetLastValidMonth = hasSameMonthAndYear(nextMonthDate, this.max);
}
if (isTargetLastValidMonth) {
if (!this.position) {
const target2 = isDirectionLeft ? this.nextMonthAction : this.prevMonthAction;
target2.disabled = false;
await target2.setFocus();
} else {
this.yearInputEl.value.focus();
}
}
}
getPx(value) {
const num = Number(value.replace(/[rem|px]/g, ""));
const base = 16;
if (value.includes("rem")) {
return `${num * base}px`;
}
return `${num}px`;
}
getYearSelectPadding() {
let padding;
switch (this.scale) {
case "l":
padding = calciteSpacingSm;
break;
case "s":
padding = calciteSpacingBase;
break;
default:
padding = calciteSpacingXxs;
break;
}
return this.getPx(padding);
}
render() {
return html`<div class=${safeClassMap(CSS.header)}>${this.renderContent()}</div>`;
}
renderContent() {
const { localeData, activeDate } = this;
if (!activeDate || !localeData) {
return null;
}
if (this.parentDatePickerEl) {
const { numberingSystem, lang: locale } = this.parentDatePickerEl;
numberStringFormatter.numberFormatOptions = {
useGrouping: false,
...numberingSystem && { numberingSystem },
...locale && { locale }
};
}
const order = getOrder(localeData.unitOrder);
const reverse = order.indexOf("y") < order.indexOf("m");
return html`${this.position && html`<div class=${safeClassMap({ [CSS.chevronContainer]: true })}>${this.position === "start" && this.renderChevron("left") || ""}</div>` || ""}<div class=${safeClassMap({
[CSS.monthYearContainer]: true,
[CSS.rangeCalendar]: !!this.position
})}>${this.renderMonthYearContainer(reverse)}</div>${!this.position && html`<div class=${safeClassMap({ [CSS.chevronContainer]: true })}>${this.renderChevron("left")}</div>` || ""}<div class=${safeClassMap({ [CSS.chevronContainer]: true })}>${this.position !== "start" && this.renderChevron("right") || ""}</div>`;
}
renderMonthYearContainer(reverse) {
const content = reverse ? [this.renderYearInput(), this.renderMonthPicker()] : [this.renderMonthPicker(), this.renderYearInput()];
return content;
}
renderMonthPicker() {
const activeMonth = this.activeDate.getMonth();
const monthData = this.localeData.months[this.monthStyle];
return html`<calcite-select class=${safeClassMap(CSS.monthPicker)} .label=${this.messages.monthMenu} @calciteSelectChange=${this.handleMonthChange} width=auto ${ref(this.monthPickerEl)}>${monthData.map((month, index) => {
return html`<calcite-option .disabled=${!this.isMonthInRange(index)} .selected=${index === activeMonth} .value=${month}>${month}</calcite-option>`;
})}</calcite-select>`;
}
renderYearInput() {
const suffix = this.localeData.year?.suffix;
const localizedYear = this.formatCalendarYear(this.activeDate.getFullYear());
return html`<span class=${safeClassMap(CSS.yearContainer)}><input .ariaLabel=${this.messages.year} class=${safeClassMap({
year: true
})} inputmode=numeric maxlength=4 minlength=1 @change=${this.onYearChange} @input=${this.onYearInput} @keydown=${this.onYearKey} pattern=\\d* type=text .value=${live(localizedYear ?? "")} ${ref(this.yearInputEl)}>${suffix && html`<span class=${safeClassMap(CSS.suffix)}>${suffix}</span>` || ""}</span>`;
}
renderChevron(direction) {
const isDirectionRight = direction === "right";
const isDisabled = hasSameMonthAndYear(isDirectionRight ? this.nextMonthDate : this.prevMonthDate, this.activeDate) || !inRange(this.activeDate, this.min, this.max);
return html`<calcite-action alignment=center .ariaDisabled=${isDisabled} .ariaLabel=${isDirectionRight ? this.messages.nextMonth : this.messages.prevMonth} class=${safeClassMap(CSS.chevron)} compact data-direction=${direction ?? nothing} .disabled=${isDisabled} .icon=${isDirectionRight ? ICON.chevronRight : ICON.chevronLeft} icon-flip-rtl @click=${isDirectionRight ? this.nextMonthClick : this.prevMonthClick} @keydown=${isDirectionRight ? this.nextMonthKeydown : this.prevMonthKeydown} role=button .scale=${this.scale === "l" ? "l" : "m"} .text=${isDirectionRight ? this.messages.nextMonth : this.messages.prevMonth} ${ref((el) => isDirectionRight ? this.nextMonthAction = el : this.prevMonthAction = el)}></calcite-action>`;
}
}
customElement("calcite-date-picker-month-header", DatePickerMonthHeader);
export {
DatePickerMonthHeader
};