@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
823 lines (822 loc) • 25.6 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 { Build, h, Host } from "@stencil/core";
import { dateFromISO, dateFromRange, dateToISO, getDaysDiff, setEndOfDay } from "../../utils/date";
import { componentLoaded, setComponentLoaded, setUpLoadableComponent } from "../../utils/loadable";
import { connectLocalized, disconnectLocalized, getDateTimeFormat, numberStringFormatter } from "../../utils/locale";
import { connectMessages, disconnectMessages, setUpMessages, updateMessages } from "../../utils/t9n";
import { DATE_PICKER_FORMAT_OPTIONS, HEADING_LEVEL } from "./resources";
import { getLocaleData, getValueAsDateRange } from "./utils";
export class DatePicker {
constructor() {
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
this.keyDownHandler = (event) => {
if (event.key === "Escape") {
this.reset();
}
};
this.monthHeaderSelectChange = (event) => {
const date = new Date(event.detail);
if (!this.range) {
this.activeDate = date;
}
else {
if (this.activeRange === "end") {
this.activeEndDate = date;
}
else {
this.activeStartDate = date;
}
this.mostRecentRangeValue = date;
}
};
this.monthActiveDateChange = (event) => {
const date = new Date(event.detail);
if (!this.range) {
this.activeDate = date;
}
else {
if (this.activeRange === "end") {
this.activeEndDate = date;
}
else {
this.activeStartDate = date;
}
this.mostRecentRangeValue = date;
}
};
this.monthHoverChange = (event) => {
if (!this.range) {
this.hoverRange = undefined;
return;
}
const { valueAsDate } = this;
const start = Array.isArray(valueAsDate) && valueAsDate[0];
const end = Array.isArray(valueAsDate) && valueAsDate[1];
const date = new Date(event.detail);
this.hoverRange = {
focused: this.activeRange || "start",
start,
end
};
if (!this.proximitySelectionDisabled) {
if (end) {
const startDiff = getDaysDiff(date, start);
const endDiff = getDaysDiff(date, end);
if (endDiff > 0) {
this.hoverRange.end = date;
this.hoverRange.focused = "end";
}
else if (startDiff < 0) {
this.hoverRange.start = date;
this.hoverRange.focused = "start";
}
else if (startDiff > endDiff) {
this.hoverRange.start = date;
this.hoverRange.focused = "start";
}
else {
this.hoverRange.end = date;
this.hoverRange.focused = "end";
}
}
else {
if (start) {
if (date < start) {
this.hoverRange = {
focused: "start",
start: date,
end: start
};
}
else {
this.hoverRange.end = date;
this.hoverRange.focused = "end";
}
}
}
}
else {
if (!end) {
if (date < start) {
this.hoverRange = {
focused: "start",
start: date,
end: start
};
}
else {
this.hoverRange.end = date;
this.hoverRange.focused = "end";
}
}
else {
this.hoverRange = undefined;
}
}
event.stopPropagation();
};
this.monthMouseOutChange = () => {
if (this.hoverRange) {
this.hoverRange = undefined;
}
};
/**
* Reset active date and close
*/
this.reset = () => {
const { valueAsDate } = this;
if (!Array.isArray(valueAsDate) &&
valueAsDate &&
valueAsDate?.getTime() !== this.activeDate?.getTime()) {
this.activeDate = new Date(valueAsDate);
}
if (Array.isArray(valueAsDate)) {
if (valueAsDate[0] &&
valueAsDate[0]?.getTime() !==
(this.activeStartDate instanceof Date && this.activeStartDate?.getTime())) {
this.activeStartDate = new Date(valueAsDate[0]);
}
if (valueAsDate[1] &&
valueAsDate[1]?.getTime() !==
(this.activeStartDate instanceof Date && this.activeEndDate?.getTime())) {
this.activeEndDate = new Date(valueAsDate[1]);
}
}
};
/**
* Event handler for when the selected date changes
*
* @param event
*/
this.monthDateChange = (event) => {
const date = new Date(event.detail);
const isoDate = dateToISO(date);
if (!this.range && isoDate === dateToISO(this.valueAsDate)) {
return;
}
if (!this.range) {
this.value = isoDate || "";
this.valueAsDate = date || null;
this.activeDate = date || null;
this.calciteDatePickerChange.emit();
return;
}
const start = this.getStartDate();
const end = this.getEndDate();
if (!start || (!end && date < start)) {
if (start) {
this.setEndDate(new Date(start));
}
if (this.activeRange == "end") {
this.setEndDate(date);
}
else {
this.setStartDate(date);
}
}
else if (!end) {
this.setEndDate(date);
}
else {
if (!this.proximitySelectionDisabled) {
if (this.activeRange) {
if (this.activeRange == "end") {
this.setEndDate(date);
}
else {
this.setStartDate(date);
}
}
else {
const startDiff = getDaysDiff(date, start);
const endDiff = getDaysDiff(date, end);
if (endDiff === 0 || startDiff < 0) {
this.setStartDate(date);
}
else if (startDiff === 0 || endDiff < 0) {
this.setEndDate(date);
}
else if (startDiff < endDiff) {
this.setStartDate(date);
}
else {
this.setEndDate(date);
}
}
}
else {
this.setStartDate(date);
}
}
this.calciteDatePickerChange.emit();
};
this.activeDate = undefined;
this.activeRange = undefined;
this.value = undefined;
this.headingLevel = undefined;
this.valueAsDate = undefined;
this.minAsDate = undefined;
this.maxAsDate = undefined;
this.min = undefined;
this.max = undefined;
this.numberingSystem = undefined;
this.scale = "m";
this.range = false;
this.proximitySelectionDisabled = false;
this.messageOverrides = undefined;
this.messages = undefined;
this.activeEndDate = undefined;
this.activeStartDate = undefined;
this.dateTimeFormat = undefined;
this.defaultMessages = undefined;
this.effectiveLocale = "";
this.endAsDate = undefined;
this.hoverRange = undefined;
this.localeData = undefined;
this.startAsDate = undefined;
}
activeDateWatcher(newActiveDate) {
if (this.activeRange === "end") {
this.activeEndDate = newActiveDate;
}
}
valueAsDateWatcher(newValueAsDate) {
if (this.range && Array.isArray(newValueAsDate)) {
const { activeStartDate, activeEndDate } = this;
const newActiveStartDate = newValueAsDate[0];
const newActiveEndDate = newValueAsDate[1];
this.activeStartDate = activeStartDate !== newActiveStartDate && newActiveStartDate;
this.activeEndDate = activeEndDate !== newActiveEndDate && newActiveEndDate;
}
else if (newValueAsDate && newValueAsDate !== this.activeDate) {
this.activeDate = newValueAsDate;
}
}
onMinChanged(min) {
if (min) {
this.minAsDate = dateFromISO(min);
}
}
onMaxChanged(max) {
if (max) {
this.maxAsDate = dateFromISO(max);
}
}
onMessagesChange() {
/* wired up by t9n util */
}
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
/** Sets focus on the component's first focusable element. */
async setFocus() {
await componentLoaded(this);
this.el.focus();
}
// --------------------------------------------------------------------------
//
// Lifecycle
//
// --------------------------------------------------------------------------
connectedCallback() {
connectLocalized(this);
connectMessages(this);
if (Array.isArray(this.value)) {
this.valueAsDate = getValueAsDateRange(this.value);
}
else if (this.value) {
this.valueAsDate = dateFromISO(this.value);
}
if (this.min) {
this.minAsDate = dateFromISO(this.min);
}
if (this.max) {
this.maxAsDate = dateFromISO(this.max);
}
}
disconnectedCallback() {
disconnectLocalized(this);
disconnectMessages(this);
}
async componentWillLoad() {
setUpLoadableComponent(this);
await this.loadLocaleData();
this.onMinChanged(this.min);
this.onMaxChanged(this.max);
await setUpMessages(this);
}
componentDidLoad() {
setComponentLoaded(this);
}
render() {
const date = dateFromRange(this.range && Array.isArray(this.valueAsDate) ? this.valueAsDate[0] : this.valueAsDate, this.minAsDate, this.maxAsDate);
let activeDate = this.getActiveDate(date, this.minAsDate, this.maxAsDate);
const endDate = this.range && Array.isArray(this.valueAsDate)
? dateFromRange(this.valueAsDate[1], this.minAsDate, this.maxAsDate)
: null;
const activeEndDate = this.getActiveEndDate(endDate, this.minAsDate, this.maxAsDate);
if ((this.activeRange === "end" ||
(this.hoverRange?.focused === "end" && (!this.proximitySelectionDisabled || endDate))) &&
activeEndDate) {
activeDate = activeEndDate;
}
if (this.range && this.mostRecentRangeValue) {
activeDate = this.mostRecentRangeValue;
}
const minDate = this.range && this.activeRange
? this.activeRange === "start"
? this.minAsDate
: date || this.minAsDate
: this.minAsDate;
const maxDate = this.range && this.activeRange
? this.activeRange === "start"
? endDate || this.maxAsDate
: this.maxAsDate
: this.maxAsDate;
return (h(Host, { onBlur: this.reset, onKeyDown: this.keyDownHandler }, this.renderCalendar(activeDate, maxDate, minDate, date, endDate)));
}
effectiveLocaleChange() {
updateMessages(this, this.effectiveLocale);
}
valueHandler(value) {
if (Array.isArray(value)) {
this.valueAsDate = getValueAsDateRange(value);
}
else if (value) {
this.valueAsDate = dateFromISO(value);
}
}
async loadLocaleData() {
if (!Build.isBrowser) {
return;
}
numberStringFormatter.numberFormatOptions = {
numberingSystem: this.numberingSystem,
locale: this.effectiveLocale,
useGrouping: false
};
this.localeData = await getLocaleData(this.effectiveLocale);
this.dateTimeFormat = getDateTimeFormat(this.effectiveLocale, DATE_PICKER_FORMAT_OPTIONS);
}
/**
* Render calcite-date-picker-month-header and calcite-date-picker-month
*
* @param activeDate
* @param maxDate
* @param minDate
* @param date
* @param endDate
*/
renderCalendar(activeDate, maxDate, minDate, date, endDate) {
return (this.localeData && [
h("calcite-date-picker-month-header", { activeDate: activeDate, headingLevel: this.headingLevel || HEADING_LEVEL, localeData: this.localeData, max: maxDate, messages: this.messages, min: minDate, onCalciteInternalDatePickerSelect: this.monthHeaderSelectChange, scale: this.scale, selectedDate: this.activeRange === "end" ? endDate : date || new Date() }),
h("calcite-date-picker-month", { activeDate: activeDate, dateTimeFormat: this.dateTimeFormat, endDate: this.range ? endDate : undefined, hoverRange: this.hoverRange, localeData: this.localeData, max: maxDate, min: minDate, onCalciteInternalDatePickerActiveDateChange: this.monthActiveDateChange, onCalciteInternalDatePickerHover: this.monthHoverChange, onCalciteInternalDatePickerMouseOut: this.monthMouseOutChange, onCalciteInternalDatePickerSelect: this.monthDateChange, scale: this.scale, selectedDate: this.activeRange === "end" ? endDate : date, startDate: this.range ? date : undefined })
]);
}
getEndDate() {
return (Array.isArray(this.valueAsDate) && this.valueAsDate[1]) || undefined;
}
setEndDate(date) {
const startDate = this.getStartDate();
const newEndDate = date ? setEndOfDay(date) : date;
this.value = [dateToISO(startDate), dateToISO(date)];
this.valueAsDate = [startDate, date];
this.mostRecentRangeValue = newEndDate;
this.calciteDatePickerRangeChange.emit();
this.activeEndDate = date || null;
}
getStartDate() {
return Array.isArray(this.valueAsDate) && this.valueAsDate[0];
}
setStartDate(date) {
const endDate = this.getEndDate();
this.value = [dateToISO(date), dateToISO(endDate)];
this.valueAsDate = [date, endDate];
this.mostRecentRangeValue = date;
this.calciteDatePickerRangeChange.emit();
this.activeStartDate = date || null;
}
/**
* Get an active date using the value, or current date as default
*
* @param value
* @param min
* @param max
*/
getActiveDate(value, min, max) {
return dateFromRange(this.activeDate, min, max) || value || dateFromRange(new Date(), min, max);
}
getActiveEndDate(value, min, max) {
return (dateFromRange(this.activeEndDate, min, max) || value || dateFromRange(new Date(), min, max));
}
static get is() { return "calcite-date-picker"; }
static get encapsulation() { return "shadow"; }
static get delegatesFocus() { return true; }
static get originalStyleUrls() {
return {
"$": ["date-picker.scss"]
};
}
static get styleUrls() {
return {
"$": ["date-picker.css"]
};
}
static get assetsDirs() { return ["assets"]; }
static get properties() {
return {
"activeDate": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Date",
"resolved": "Date",
"references": {
"Date": {
"location": "global"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the component's active date."
}
},
"activeRange": {
"type": "string",
"mutable": false,
"complexType": {
"original": "\"start\" | \"end\"",
"resolved": "\"end\" | \"start\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When `range` is true, specifies the active `range`. Where `\"start\"` specifies the starting range date and `\"end\"` the ending range date."
},
"attribute": "active-range",
"reflect": true
},
"value": {
"type": "string",
"mutable": true,
"complexType": {
"original": "string | string[]",
"resolved": "string | string[]",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the selected date as a string (`\"yyyy-mm-dd\"`), or an array of strings for `range` values (`[\"yyyy-mm-dd\", \"yyyy-mm-dd\"]`)."
},
"attribute": "value",
"reflect": false
},
"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": true
},
"valueAsDate": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Date | Date[]",
"resolved": "Date | Date[]",
"references": {
"Date": {
"location": "global"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the selected date as a full date object (`new Date(\"yyyy-mm-dd\")`), or an array containing full date objects (`[new Date(\"yyyy-mm-dd\"), new Date(\"yyyy-mm-dd\")]`)."
}
},
"minAsDate": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Date",
"resolved": "Date",
"references": {
"Date": {
"location": "global"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the earliest allowed date as a full date object (`new Date(\"yyyy-mm-dd\")`)."
}
},
"maxAsDate": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Date",
"resolved": "Date",
"references": {
"Date": {
"location": "global"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the latest allowed date as a full date object (`new Date(\"yyyy-mm-dd\")`)."
}
},
"min": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the earliest allowed date (`\"yyyy-mm-dd\"`)."
},
"attribute": "min",
"reflect": true
},
"max": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the latest allowed date (`\"yyyy-mm-dd\"`)."
},
"attribute": "max",
"reflect": true
},
"numberingSystem": {
"type": "string",
"mutable": false,
"complexType": {
"original": "NumberingSystem",
"resolved": "\"arab\" | \"arabext\" | \"bali\" | \"beng\" | \"deva\" | \"fullwide\" | \"gujr\" | \"guru\" | \"hanidec\" | \"khmr\" | \"knda\" | \"laoo\" | \"latn\" | \"limb\" | \"mlym\" | \"mong\" | \"mymr\" | \"orya\" | \"tamldec\" | \"telu\" | \"thai\" | \"tibt\"",
"references": {
"NumberingSystem": {
"location": "import",
"path": "../../utils/locale"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the Unicode numeral system used by the component for localization. This property cannot be dynamically changed."
},
"attribute": "numbering-system",
"reflect": true
},
"scale": {
"type": "string",
"mutable": false,
"complexType": {
"original": "\"s\" | \"m\" | \"l\"",
"resolved": "\"l\" | \"m\" | \"s\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Specifies the size of the component."
},
"attribute": "scale",
"reflect": true,
"defaultValue": "\"m\""
},
"range": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When `true`, activates the component's range mode to allow a start and end date."
},
"attribute": "range",
"reflect": true,
"defaultValue": "false"
},
"proximitySelectionDisabled": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When `true`, disables the default behavior on the third click of narrowing or extending the range and instead starts a new range."
},
"attribute": "proximity-selection-disabled",
"reflect": true,
"defaultValue": "false"
},
"messageOverrides": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Partial<DatePickerMessages>",
"resolved": "{ nextMonth?: string; prevMonth?: string; year?: string; }",
"references": {
"Partial": {
"location": "global"
},
"DatePickerMessages": {
"location": "import",
"path": "./assets/date-picker/t9n"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Use this property to override individual strings used by the component."
}
},
"messages": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "DatePickerMessages",
"resolved": "{ nextMonth: string; prevMonth: string; year: string; }",
"references": {
"DatePickerMessages": {
"location": "import",
"path": "./assets/date-picker/t9n"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "internal",
"text": undefined
}],
"text": "Made into a prop for testing purposes only"
}
}
};
}
static get states() {
return {
"activeEndDate": {},
"activeStartDate": {},
"dateTimeFormat": {},
"defaultMessages": {},
"effectiveLocale": {},
"endAsDate": {},
"hoverRange": {},
"localeData": {},
"startAsDate": {}
};
}
static get events() {
return [{
"method": "calciteDatePickerChange",
"name": "calciteDatePickerChange",
"bubbles": true,
"cancelable": false,
"composed": true,
"docs": {
"tags": [],
"text": "Emits when a user changes the component's date. For `range` events, use `calciteDatePickerRangeChange`."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "calciteDatePickerRangeChange",
"name": "calciteDatePickerRangeChange",
"bubbles": true,
"cancelable": false,
"composed": true,
"docs": {
"tags": [],
"text": "Emits when a user changes the component's date `range`. For components without `range` use `calciteDatePickerChange`."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}];
}
static get methods() {
return {
"setFocus": {
"complexType": {
"signature": "() => Promise<void>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "Sets focus on the component's first focusable element.",
"tags": []
}
}
};
}
static get elementRef() { return "el"; }
static get watchers() {
return [{
"propName": "activeDate",
"methodName": "activeDateWatcher"
}, {
"propName": "valueAsDate",
"methodName": "valueAsDateWatcher"
}, {
"propName": "min",
"methodName": "onMinChanged"
}, {
"propName": "max",
"methodName": "onMaxChanged"
}, {
"propName": "messageOverrides",
"methodName": "onMessagesChange"
}, {
"propName": "effectiveLocale",
"methodName": "effectiveLocaleChange"
}, {
"propName": "value",
"methodName": "valueHandler"
}, {
"propName": "effectiveLocale",
"methodName": "loadLocaleData"
}];
}
}