UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

278 lines (277 loc) • 18.3 kB
/*! 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 };