@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
398 lines (397 loc) • 13.2 kB
JavaScript
/*!
* 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"
}];
}
}