UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

398 lines (397 loc) • 13.2 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.5.0-next.4 */ import { Fragment, h } from "@stencil/core"; import { dateFromRange, parseCalendarYear, getOrder, nextMonth, prevMonth, formatCalendarYear } from "../../utils/date"; import { closestElementCrossShadowBoundary } from "../../utils/dom"; import { isActivationKey } from "../../utils/key"; import { numberStringFormatter } from "../../utils/locale"; import { Heading } from "../functional/Heading"; import { CSS, ICON } from "./resources"; export class DatePickerMonthHeader { constructor() { //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * Increment year on UP/DOWN keys * * @param event */ this.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; } }; this.onYearChange = (event) => { this.setYear({ localizedYear: this.parseCalendarYear(event.target.value) }); }; this.onYearInput = (event) => { this.setYear({ localizedYear: this.parseCalendarYear(event.target.value), commit: false }); }; this.prevMonthClick = (event) => { this.handleArrowClick(event, this.prevMonthDate); }; this.prevMonthKeydown = (event) => { if (isActivationKey(event.key)) { this.prevMonthClick(event); } }; this.nextMonthClick = (event) => { this.handleArrowClick(event, this.nextMonthDate); }; this.nextMonthKeydown = (event) => { if (isActivationKey(event.key)) { this.nextMonthClick(event); } }; /* * Update active month on clicks of left/right arrows */ this.handleArrowClick = (event, date) => { event.preventDefault(); this.calciteInternalDatePickerSelect.emit(date); }; this.selectedDate = undefined; this.activeDate = undefined; this.headingLevel = undefined; this.min = undefined; this.max = undefined; this.scale = undefined; this.localeData = undefined; this.messages = undefined; this.nextMonthDate = undefined; this.prevMonthDate = undefined; } //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- componentWillLoad() { this.parentDatePickerEl = closestElementCrossShadowBoundary(this.el, "calcite-date-picker"); } connectedCallback() { this.setNextPrevMonthDates(); } render() { return h("div", { class: CSS.header }, this.renderContent()); } renderContent() { const { messages, 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 activeMonth = activeDate.getMonth(); const { months, unitOrder } = localeData; const localizedMonth = (months.wide || months.narrow || months.abbreviated)[activeMonth]; const localizedYear = this.formatCalendarYear(activeDate.getFullYear()); const iconScale = this.scale === "l" ? "m" : "s"; const order = getOrder(unitOrder); const reverse = order.indexOf("y") < order.indexOf("m"); const suffix = localeData.year?.suffix; return (h(Fragment, null, h("a", { "aria-disabled": `${this.prevMonthDate.getMonth() === activeMonth}`, "aria-label": messages.prevMonth, class: CSS.chevron, href: "#", onClick: this.prevMonthClick, onKeyDown: this.prevMonthKeydown, role: "button", tabindex: this.prevMonthDate.getMonth() === activeMonth ? -1 : 0 }, h("calcite-icon", { "flip-rtl": true, icon: ICON.chevronLeft, scale: iconScale })), h("div", { class: { text: true, [CSS.textReverse]: reverse } }, h(Heading, { class: CSS.month, level: this.headingLevel }, localizedMonth), h("span", { class: CSS.yearWrap }, h("input", { "aria-label": messages.year, class: { year: true, [CSS.yearSuffix]: !!suffix }, inputmode: "numeric", maxlength: "4", minlength: "1", onChange: this.onYearChange, onInput: this.onYearInput, onKeyDown: this.onYearKey, pattern: "\\d*", type: "text", value: localizedYear, // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.yearInput = el) }), suffix && h("span", { class: CSS.suffix }, suffix))), h("a", { "aria-disabled": `${this.nextMonthDate.getMonth() === activeMonth}`, "aria-label": messages.nextMonth, class: CSS.chevron, href: "#", onClick: this.nextMonthClick, onKeyDown: this.nextMonthKeydown, role: "button", tabindex: this.nextMonthDate.getMonth() === activeMonth ? -1 : 0 }, h("calcite-icon", { "flip-rtl": true, icon: ICON.chevronRight, scale: iconScale })))); } 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); } formatCalendarYear(year) { return numberStringFormatter.localize(`${formatCalendarYear(year, this.localeData)}`); } parseCalendarYear(year) { return numberStringFormatter.localize(`${parseCalendarYear(Number(numberStringFormatter.delocalize(year)), this.localeData)}`); } 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 inRange = year && (!min || min.getFullYear() <= year) && (!max || max.getFullYear() >= year); // if you've supplied a year and it's in range if (year && inRange && length === localizedYear.length) { const nextDate = new Date(activeDate); nextDate.setFullYear(year); return dateFromRange(nextDate, min, max); } } /** * Parse localized year string from input, * set to active if in range * * @param root0 * @param root0.localizedYear * @param root0.commit * @param root0.offset */ setYear({ localizedYear, commit = true, offset = 0 }) { const { yearInput, activeDate } = this; const inRangeDate = this.getInRangeDate({ localizedYear, offset }); // if you've supplied a year and it's in range, update active date if (inRangeDate) { this.calciteInternalDatePickerSelect.emit(inRangeDate); } if (commit) { yearInput.value = this.formatCalendarYear((inRangeDate || activeDate).getFullYear()); } } static get is() { return "calcite-date-picker-month-header"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["date-picker-month-header.scss"] }; } static get styleUrls() { return { "$": ["date-picker-month-header.css"] }; } static get properties() { return { "selectedDate": { "type": "unknown", "mutable": false, "complexType": { "original": "Date", "resolved": "Date", "references": { "Date": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Already selected date." } }, "activeDate": { "type": "unknown", "mutable": false, "complexType": { "original": "Date", "resolved": "Date", "references": { "Date": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Focused date with indicator (will become selected date if user proceeds)" } }, "headingLevel": { "type": "number", "mutable": false, "complexType": { "original": "HeadingLevel", "resolved": "1 | 2 | 3 | 4 | 5 | 6", "references": { "HeadingLevel": { "location": "import", "path": "../functional/Heading" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the number at which section headings should start." }, "attribute": "heading-level", "reflect": false }, "min": { "type": "unknown", "mutable": false, "complexType": { "original": "Date", "resolved": "Date", "references": { "Date": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the earliest allowed date (`\"yyyy-mm-dd\"`)." } }, "max": { "type": "unknown", "mutable": false, "complexType": { "original": "Date", "resolved": "Date", "references": { "Date": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the latest allowed date (`\"yyyy-mm-dd\"`)." } }, "scale": { "type": "string", "mutable": false, "complexType": { "original": "Scale", "resolved": "\"l\" | \"m\" | \"s\"", "references": { "Scale": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the size of the component." }, "attribute": "scale", "reflect": true }, "localeData": { "type": "unknown", "mutable": false, "complexType": { "original": "DateLocaleData", "resolved": "DateLocaleData", "references": { "DateLocaleData": { "location": "import", "path": "../date-picker/utils" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "CLDR locale data for translated calendar info" } }, "messages": { "type": "unknown", "mutable": true, "complexType": { "original": "DatePickerMessages", "resolved": "{ nextMonth: string; prevMonth: string; year: string; }", "references": { "DatePickerMessages": { "location": "import", "path": "../date-picker/assets/date-picker/t9n" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }, { "name": "readonly", "text": undefined }], "text": "This property specifies accessible strings for the component's previous month button ,next month button & year input elements.\nMade into a prop for testing purposes only." } } }; } static get states() { return { "nextMonthDate": {}, "prevMonthDate": {} }; } static get events() { return [{ "method": "calciteInternalDatePickerSelect", "name": "calciteInternalDatePickerSelect", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "Changes to active date" }, "complexType": { "original": "Date", "resolved": "Date", "references": { "Date": { "location": "global" } } } }]; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "min", "methodName": "setNextPrevMonthDates" }, { "propName": "max", "methodName": "setNextPrevMonthDates" }, { "propName": "activeDate", "methodName": "setNextPrevMonthDates" }]; } }