date-picker-month-header.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.82
  5. */
  6. import { Component, Element, Prop, Event, h, State, Watch, Fragment } from "@stencil/core";
  7. import { dateFromRange, nextMonth, prevMonth, localizeNumber, parseNumber, getOrder } from "../../utils/date";
  8. import { Heading } from "../functional/Heading";
  9. export class DatePickerMonthHeader {
  10. constructor() {
  11. //--------------------------------------------------------------------------
  12. //
  13. // Private Methods
  14. //
  15. //--------------------------------------------------------------------------
  16. /**
  17. * Increment year on UP/DOWN keys
  18. */
  19. this.onYearKey = (e) => {
  20. const localizedYear = e.target.value;
  21. switch (e.key) {
  22. case "ArrowDown":
  23. e.preventDefault();
  24. this.setYear({ localizedYear, offset: -1 });
  25. break;
  26. case "ArrowUp":
  27. e.preventDefault();
  28. this.setYear({ localizedYear, offset: 1 });
  29. break;
  30. }
  31. };
  32. this.onYearChange = (event) => {
  33. this.setYear({ localizedYear: event.target.value });
  34. };
  35. this.onYearInput = (event) => {
  36. this.setYear({ localizedYear: event.target.value, commit: false });
  37. };
  38. this.prevMonthClick = (e) => {
  39. this.handleArrowClick(e, this.prevMonthDate);
  40. };
  41. this.prevMonthKeydown = (e) => {
  42. const key = e.key;
  43. if (key === " " || key === "Enter") {
  44. this.prevMonthClick(e);
  45. }
  46. };
  47. this.nextMonthClick = (e) => {
  48. this.handleArrowClick(e, this.nextMonthDate);
  49. };
  50. this.nextMonthKeydown = (e) => {
  51. const key = e.key;
  52. if (key === " " || key === "Enter") {
  53. this.nextMonthClick(e);
  54. }
  55. };
  56. /*
  57. * Update active month on clicks of left/right arrows
  58. */
  59. this.handleArrowClick = (e, date) => {
  60. e === null || e === void 0 ? void 0 : e.preventDefault();
  61. e.stopPropagation();
  62. this.calciteDatePickerSelect.emit(date);
  63. };
  64. }
  65. //--------------------------------------------------------------------------
  66. //
  67. // Lifecycle
  68. //
  69. //--------------------------------------------------------------------------
  70. connectedCallback() {
  71. this.setNextPrevMonthDates();
  72. }
  73. render() {
  74. return h("div", { class: "header" }, this.renderContent());
  75. }
  76. renderContent() {
  77. var _a;
  78. if (!this.activeDate || !this.localeData) {
  79. return null;
  80. }
  81. const activeMonth = this.activeDate.getMonth();
  82. const { months, unitOrder } = this.localeData;
  83. const localizedMonth = (months.wide || months.narrow || months.abbreviated)[activeMonth];
  84. const localizedYear = localizeNumber(this.activeDate.getFullYear(), this.localeData);
  85. const iconScale = this.scale === "l" ? "m" : "s";
  86. const order = getOrder(unitOrder);
  87. const reverse = order.indexOf("y") < order.indexOf("m");
  88. const suffix = (_a = this.localeData.year) === null || _a === void 0 ? void 0 : _a.suffix;
  89. return (h(Fragment, null,
  90. h("a", { "aria-disabled": (this.prevMonthDate.getMonth() === activeMonth).toString(), "aria-label": this.intlPrevMonth, class: "chevron", href: "#", onClick: this.prevMonthClick, onKeyDown: this.prevMonthKeydown, role: "button", tabindex: this.prevMonthDate.getMonth() === activeMonth ? -1 : 0 },
  91. h("calcite-icon", { "flip-rtl": true, icon: "chevron-left", scale: iconScale })),
  92. h("div", { class: { text: true, "text--reverse": reverse } },
  93. h(Heading, { class: "month", level: this.headingLevel }, localizedMonth),
  94. h("span", { class: "year-wrap" },
  95. h("input", { "aria-label": this.intlYear, class: {
  96. year: true,
  97. "year--suffix": !!suffix
  98. }, inputmode: "numeric", maxlength: "4", minlength: "1", onChange: this.onYearChange, onInput: this.onYearInput, onKeyDown: this.onYearKey, pattern: "\\d*", ref: (el) => (this.yearInput = el), type: "text", value: localizedYear }),
  99. suffix && (h("span", { class: "suffix" },
  100. h("span", { "aria-hidden": "true", class: "suffix__invisible" }, localizedYear),
  101. " " + suffix)))),
  102. h("a", { "aria-disabled": (this.nextMonthDate.getMonth() === activeMonth).toString(), "aria-label": this.intlNextMonth, class: "chevron", href: "#", onClick: this.nextMonthClick, onKeyDown: this.nextMonthKeydown, role: "button", tabindex: this.nextMonthDate.getMonth() === activeMonth ? -1 : 0 },
  103. h("calcite-icon", { "flip-rtl": true, icon: "chevron-right", scale: iconScale }))));
  104. }
  105. setNextPrevMonthDates() {
  106. if (!this.activeDate) {
  107. return;
  108. }
  109. this.nextMonthDate = dateFromRange(nextMonth(this.activeDate), this.min, this.max);
  110. this.prevMonthDate = dateFromRange(prevMonth(this.activeDate), this.min, this.max);
  111. }
  112. getInRangeDate({ localizedYear, offset = 0 }) {
  113. const { min, max, activeDate, localeData } = this;
  114. const parsedYear = parseNumber(localizedYear, localeData);
  115. const length = parsedYear.toString().length;
  116. const year = isNaN(parsedYear) ? false : parsedYear + offset;
  117. const inRange = year && (!min || min.getFullYear() <= year) && (!max || max.getFullYear() >= year);
  118. // if you've supplied a year and it's in range
  119. if (year && inRange && length === localizedYear.length) {
  120. const nextDate = new Date(activeDate);
  121. nextDate.setFullYear(year);
  122. return dateFromRange(nextDate, min, max);
  123. }
  124. }
  125. /**
  126. * Parse localized year string from input,
  127. * set to active if in range
  128. */
  129. setYear({ localizedYear, commit = true, offset = 0 }) {
  130. const { yearInput, activeDate, localeData } = this;
  131. const inRangeDate = this.getInRangeDate({ localizedYear, offset });
  132. // if you've supplied a year and it's in range, update active date
  133. if (inRangeDate) {
  134. this.calciteDatePickerSelect.emit(inRangeDate);
  135. }
  136. if (commit) {
  137. yearInput.value = localizeNumber((inRangeDate || activeDate).getFullYear(), localeData);
  138. }
  139. }
  140. static get is() { return "calcite-date-picker-month-header"; }
  141. static get encapsulation() { return "shadow"; }
  142. static get originalStyleUrls() { return {
  143. "$": ["date-picker-month-header.scss"]
  144. }; }
  145. static get styleUrls() { return {
  146. "$": ["date-picker-month-header.css"]
  147. }; }
  148. static get properties() { return {
  149. "selectedDate": {
  150. "type": "unknown",
  151. "mutable": false,
  152. "complexType": {
  153. "original": "Date",
  154. "resolved": "Date",
  155. "references": {
  156. "Date": {
  157. "location": "global"
  158. }
  159. }
  160. },
  161. "required": false,
  162. "optional": false,
  163. "docs": {
  164. "tags": [],
  165. "text": "Already selected date."
  166. }
  167. },
  168. "activeDate": {
  169. "type": "unknown",
  170. "mutable": false,
  171. "complexType": {
  172. "original": "Date",
  173. "resolved": "Date",
  174. "references": {
  175. "Date": {
  176. "location": "global"
  177. }
  178. }
  179. },
  180. "required": false,
  181. "optional": false,
  182. "docs": {
  183. "tags": [],
  184. "text": "Focused date with indicator (will become selected date if user proceeds)"
  185. }
  186. },
  187. "headingLevel": {
  188. "type": "number",
  189. "mutable": false,
  190. "complexType": {
  191. "original": "HeadingLevel",
  192. "resolved": "1 | 2 | 3 | 4 | 5 | 6",
  193. "references": {
  194. "HeadingLevel": {
  195. "location": "import",
  196. "path": "../functional/Heading"
  197. }
  198. }
  199. },
  200. "required": false,
  201. "optional": false,
  202. "docs": {
  203. "tags": [],
  204. "text": "Number at which section headings should start for this component."
  205. },
  206. "attribute": "heading-level",
  207. "reflect": false
  208. },
  209. "min": {
  210. "type": "unknown",
  211. "mutable": false,
  212. "complexType": {
  213. "original": "Date",
  214. "resolved": "Date",
  215. "references": {
  216. "Date": {
  217. "location": "global"
  218. }
  219. }
  220. },
  221. "required": false,
  222. "optional": false,
  223. "docs": {
  224. "tags": [],
  225. "text": "Minimum date of the calendar below which is disabled."
  226. }
  227. },
  228. "max": {
  229. "type": "unknown",
  230. "mutable": false,
  231. "complexType": {
  232. "original": "Date",
  233. "resolved": "Date",
  234. "references": {
  235. "Date": {
  236. "location": "global"
  237. }
  238. }
  239. },
  240. "required": false,
  241. "optional": false,
  242. "docs": {
  243. "tags": [],
  244. "text": "Maximum date of the calendar above which is disabled."
  245. }
  246. },
  247. "locale": {
  248. "type": "string",
  249. "mutable": false,
  250. "complexType": {
  251. "original": "string",
  252. "resolved": "string",
  253. "references": {}
  254. },
  255. "required": false,
  256. "optional": false,
  257. "docs": {
  258. "tags": [],
  259. "text": "User's language and region as BCP 47 formatted string."
  260. },
  261. "attribute": "locale",
  262. "reflect": false
  263. },
  264. "intlPrevMonth": {
  265. "type": "string",
  266. "mutable": false,
  267. "complexType": {
  268. "original": "string",
  269. "resolved": "string",
  270. "references": {}
  271. },
  272. "required": false,
  273. "optional": false,
  274. "docs": {
  275. "tags": [],
  276. "text": "Localized string for previous month."
  277. },
  278. "attribute": "intl-prev-month",
  279. "reflect": false
  280. },
  281. "intlNextMonth": {
  282. "type": "string",
  283. "mutable": false,
  284. "complexType": {
  285. "original": "string",
  286. "resolved": "string",
  287. "references": {}
  288. },
  289. "required": false,
  290. "optional": false,
  291. "docs": {
  292. "tags": [],
  293. "text": "Localized string for next month."
  294. },
  295. "attribute": "intl-next-month",
  296. "reflect": false
  297. },
  298. "intlYear": {
  299. "type": "string",
  300. "mutable": false,
  301. "complexType": {
  302. "original": "string",
  303. "resolved": "string",
  304. "references": {}
  305. },
  306. "required": false,
  307. "optional": false,
  308. "docs": {
  309. "tags": [],
  310. "text": "Localized string for year."
  311. },
  312. "attribute": "intl-year",
  313. "reflect": false
  314. },
  315. "scale": {
  316. "type": "string",
  317. "mutable": false,
  318. "complexType": {
  319. "original": "Scale",
  320. "resolved": "\"l\" | \"m\" | \"s\"",
  321. "references": {
  322. "Scale": {
  323. "location": "import",
  324. "path": "../interfaces"
  325. }
  326. }
  327. },
  328. "required": false,
  329. "optional": false,
  330. "docs": {
  331. "tags": [],
  332. "text": "specify the scale of the date picker"
  333. },
  334. "attribute": "scale",
  335. "reflect": true
  336. },
  337. "localeData": {
  338. "type": "unknown",
  339. "mutable": false,
  340. "complexType": {
  341. "original": "DateLocaleData",
  342. "resolved": "DateLocaleData",
  343. "references": {
  344. "DateLocaleData": {
  345. "location": "import",
  346. "path": "../date-picker/utils"
  347. }
  348. }
  349. },
  350. "required": false,
  351. "optional": false,
  352. "docs": {
  353. "tags": [],
  354. "text": "CLDR locale data for translated calendar info"
  355. }
  356. }
  357. }; }
  358. static get states() { return {
  359. "nextMonthDate": {},
  360. "prevMonthDate": {}
  361. }; }
  362. static get events() { return [{
  363. "method": "calciteDatePickerSelect",
  364. "name": "calciteDatePickerSelect",
  365. "bubbles": true,
  366. "cancelable": true,
  367. "composed": true,
  368. "docs": {
  369. "tags": [],
  370. "text": "Changes to active date"
  371. },
  372. "complexType": {
  373. "original": "Date",
  374. "resolved": "Date",
  375. "references": {
  376. "Date": {
  377. "location": "global"
  378. }
  379. }
  380. }
  381. }]; }
  382. static get elementRef() { return "el"; }
  383. static get watchers() { return [{
  384. "propName": "min",
  385. "methodName": "setNextPrevMonthDates"
  386. }, {
  387. "propName": "max",
  388. "methodName": "setNextPrevMonthDates"
  389. }, {
  390. "propName": "activeDate",
  391. "methodName": "setNextPrevMonthDates"
  392. }]; }
  393. }