date-picker.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  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 { getAssetPath, proxyCustomElement, HTMLElement, createEvent, h, Host, Build } from '@stencil/core/internal/client';
  7. import { a as locales } from './locale.js';
  8. import { g as getDaysDiff, d as dateToISO, a as dateFromISO, b as dateFromRange } from './date.js';
  9. import { d as defineCustomElement$4 } from './date-picker-day.js';
  10. import { d as defineCustomElement$3 } from './date-picker-month.js';
  11. import { d as defineCustomElement$2 } from './date-picker-month-header.js';
  12. import { d as defineCustomElement$1 } from './icon.js';
  13. /**
  14. * Get supported locale code from raw user input
  15. * Exported for testing purposes.
  16. * @private
  17. */
  18. function getSupportedLocale(lang = "") {
  19. if (locales.indexOf(lang) > -1) {
  20. return lang;
  21. }
  22. lang = lang.toLowerCase();
  23. if (lang.includes("-")) {
  24. lang = lang.replace(/(\w+)-(\w+)/, (_match, language, region) => `${language}-${region.toUpperCase()}`);
  25. if (!locales.includes(lang)) {
  26. lang = lang.split("-")[0];
  27. }
  28. }
  29. return locales.includes(lang) ? lang : "en";
  30. }
  31. /**
  32. * CLDR cache.
  33. * Exported for testing purposes.
  34. * @private
  35. */
  36. const translationCache = {};
  37. /**
  38. * CLDR request cache.
  39. * Exported for testing purposes.
  40. * @private
  41. */
  42. const requestCache = {};
  43. /**
  44. * Fetch calendar data for a given locale from list of supported languages
  45. * @public
  46. */
  47. async function getLocaleData(lang) {
  48. const locale = getSupportedLocale(lang);
  49. if (translationCache[locale]) {
  50. return translationCache[locale];
  51. }
  52. if (!requestCache[locale]) {
  53. requestCache[locale] = fetch(getAssetPath(`./assets/date-picker/nls/${locale}.json`))
  54. .then((resp) => resp.json())
  55. .catch(() => {
  56. console.error(`Translations for "${locale}" not found or invalid, falling back to english`);
  57. return getLocaleData("en");
  58. });
  59. }
  60. const data = await requestCache[locale];
  61. translationCache[locale] = data;
  62. return data;
  63. }
  64. const HEADING_LEVEL = 2;
  65. const TEXT = {
  66. nextMonth: "Next month",
  67. prevMonth: "Previous month",
  68. year: "Year"
  69. };
  70. const datePickerCss = "@-webkit-keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}@keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:var(--calcite-animation-timing);animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{-webkit-animation-name:in;animation-name:in}.calcite-animate__in-down{-webkit-animation-name:in-down;animation-name:in-down}.calcite-animate__in-up{-webkit-animation-name:in-up;animation-name:in-up}.calcite-animate__in-scale{-webkit-animation-name:in-scale;animation-name:in-scale}:root{--calcite-popper-transition:var(--calcite-animation-timing)}:host([hidden]){display:none}:host{position:relative;display:inline-block;width:100%;overflow:visible;border-radius:0px;border-width:1px;border-style:solid;border-color:var(--calcite-ui-border-2);background-color:var(--calcite-ui-foreground-1);vertical-align:top}:host([scale=s]){max-width:216px}:host([scale=m]){max-width:286px}:host([scale=l]){max-width:398px}";
  71. const DatePicker = /*@__PURE__*/ proxyCustomElement(class extends HTMLElement {
  72. constructor() {
  73. super();
  74. this.__registerHost();
  75. this.__attachShadow();
  76. this.calciteDatePickerChange = createEvent(this, "calciteDatePickerChange", 7);
  77. this.calciteDatePickerRangeChange = createEvent(this, "calciteDatePickerRangeChange", 7);
  78. /** Localized string for "previous month" (used for aria label)
  79. * @default "Previous month"
  80. */
  81. this.intlPrevMonth = TEXT.prevMonth;
  82. /** Localized string for "next month" (used for aria label)
  83. * @default "Next month"
  84. */
  85. this.intlNextMonth = TEXT.nextMonth;
  86. /** Localized string for "year" (used for aria label)
  87. * @default "Year"
  88. */
  89. this.intlYear = TEXT.year;
  90. /** BCP 47 language tag for desired language and country format */
  91. this.locale = document.documentElement.lang || "en";
  92. /** specify the scale of the date picker */
  93. this.scale = "m";
  94. /** Range mode activation */
  95. this.range = false;
  96. /** Disables the default behaviour on the third click of narrowing or extending the range and instead starts a new range. */
  97. this.proximitySelectionDisabled = false;
  98. //--------------------------------------------------------------------------
  99. //
  100. // Private Methods
  101. //
  102. //--------------------------------------------------------------------------
  103. this.keyUpHandler = (e) => {
  104. if (e.key === "Escape") {
  105. this.reset();
  106. }
  107. };
  108. this.monthHeaderSelectChange = (e) => {
  109. const date = new Date(e.detail);
  110. if (!this.range) {
  111. this.activeDate = date;
  112. }
  113. else {
  114. if (this.activeRange === "end") {
  115. this.activeEndDate = date;
  116. }
  117. else {
  118. this.activeStartDate = date;
  119. }
  120. this.mostRecentRangeValue = date;
  121. }
  122. };
  123. this.monthActiveDateChange = (e) => {
  124. const date = new Date(e.detail);
  125. if (!this.range) {
  126. this.activeDate = date;
  127. }
  128. else {
  129. if (this.activeRange === "end") {
  130. this.activeEndDate = date;
  131. }
  132. else {
  133. this.activeStartDate = date;
  134. }
  135. this.mostRecentRangeValue = date;
  136. }
  137. };
  138. this.monthHoverChange = (e) => {
  139. if (!this.startAsDate) {
  140. this.hoverRange = undefined;
  141. return;
  142. }
  143. const date = new Date(e.detail);
  144. this.hoverRange = {
  145. focused: this.activeRange || "start",
  146. start: this.startAsDate,
  147. end: this.endAsDate
  148. };
  149. if (!this.proximitySelectionDisabled) {
  150. if (this.endAsDate) {
  151. const startDiff = getDaysDiff(date, this.startAsDate);
  152. const endDiff = getDaysDiff(date, this.endAsDate);
  153. if (endDiff > 0) {
  154. this.hoverRange.end = date;
  155. this.hoverRange.focused = "end";
  156. }
  157. else if (startDiff < 0) {
  158. this.hoverRange.start = date;
  159. this.hoverRange.focused = "start";
  160. }
  161. else if (startDiff > endDiff) {
  162. this.hoverRange.start = date;
  163. this.hoverRange.focused = "start";
  164. }
  165. else {
  166. this.hoverRange.end = date;
  167. this.hoverRange.focused = "end";
  168. }
  169. }
  170. else {
  171. if (date < this.startAsDate) {
  172. this.hoverRange = {
  173. focused: "start",
  174. start: date,
  175. end: this.startAsDate
  176. };
  177. }
  178. else {
  179. this.hoverRange.end = date;
  180. this.hoverRange.focused = "end";
  181. }
  182. }
  183. }
  184. else {
  185. if (!this.endAsDate) {
  186. if (date < this.startAsDate) {
  187. this.hoverRange = {
  188. focused: "start",
  189. start: date,
  190. end: this.startAsDate
  191. };
  192. }
  193. else {
  194. this.hoverRange.end = date;
  195. this.hoverRange.focused = "end";
  196. }
  197. }
  198. else {
  199. this.hoverRange = undefined;
  200. }
  201. }
  202. };
  203. this.monthMouseOutChange = () => {
  204. if (this.hoverRange) {
  205. this.hoverRange = undefined;
  206. }
  207. };
  208. /**
  209. * Reset active date and close
  210. */
  211. this.reset = () => {
  212. var _a, _b, _c, _d, _e, _f;
  213. if (!Array.isArray(this.valueAsDate) &&
  214. this.valueAsDate &&
  215. ((_a = this.valueAsDate) === null || _a === void 0 ? void 0 : _a.getTime()) !== ((_b = this.activeDate) === null || _b === void 0 ? void 0 : _b.getTime())) {
  216. this.activeDate = new Date(this.valueAsDate);
  217. }
  218. if (this.startAsDate && ((_c = this.startAsDate) === null || _c === void 0 ? void 0 : _c.getTime()) !== ((_d = this.activeStartDate) === null || _d === void 0 ? void 0 : _d.getTime())) {
  219. this.activeStartDate = new Date(this.startAsDate);
  220. }
  221. if (this.endAsDate && ((_e = this.endAsDate) === null || _e === void 0 ? void 0 : _e.getTime()) !== ((_f = this.activeEndDate) === null || _f === void 0 ? void 0 : _f.getTime())) {
  222. this.activeEndDate = new Date(this.endAsDate);
  223. }
  224. };
  225. /**
  226. * Event handler for when the selected date changes
  227. */
  228. this.monthDateChange = (e) => {
  229. const date = new Date(e.detail);
  230. if (!this.range) {
  231. this.value = date ? dateToISO(date) : "";
  232. this.valueAsDate = date || null;
  233. this.activeDate = date || null;
  234. this.calciteDatePickerChange.emit(date);
  235. return;
  236. }
  237. if (!this.startAsDate || (!this.endAsDate && date < this.startAsDate)) {
  238. if (this.startAsDate) {
  239. this.setEndDate(new Date(this.startAsDate));
  240. }
  241. if (this.activeRange == "end") {
  242. this.setEndDate(date);
  243. }
  244. else {
  245. this.setStartDate(date);
  246. }
  247. }
  248. else if (!this.endAsDate) {
  249. this.setEndDate(date);
  250. }
  251. else {
  252. if (!this.proximitySelectionDisabled) {
  253. if (this.activeRange) {
  254. if (this.activeRange == "end") {
  255. this.setEndDate(date);
  256. }
  257. else {
  258. this.setStartDate(date);
  259. }
  260. }
  261. else {
  262. const startDiff = getDaysDiff(date, this.startAsDate);
  263. const endDiff = getDaysDiff(date, this.endAsDate);
  264. if (endDiff === 0 || startDiff < 0) {
  265. this.setStartDate(date);
  266. }
  267. else if (startDiff === 0 || endDiff < 0) {
  268. this.setEndDate(date);
  269. }
  270. else if (startDiff < endDiff) {
  271. this.setStartDate(date);
  272. }
  273. else {
  274. this.setEndDate(date);
  275. }
  276. }
  277. }
  278. else {
  279. this.setStartDate(date);
  280. this.endAsDate = this.activeEndDate = this.end = undefined;
  281. }
  282. }
  283. this.calciteDatePickerChange.emit(date);
  284. };
  285. }
  286. handleValueAsDate(date) {
  287. if (!Array.isArray(date) && date && date !== this.activeDate) {
  288. this.activeDate = date;
  289. }
  290. }
  291. handleRangeChange() {
  292. const { startAsDate: startDate, endAsDate: endDate } = this;
  293. this.activeEndDate = endDate;
  294. this.activeStartDate = startDate;
  295. }
  296. onMinChanged(min) {
  297. if (min) {
  298. this.minAsDate = dateFromISO(min);
  299. }
  300. }
  301. onMaxChanged(max) {
  302. if (max) {
  303. this.maxAsDate = dateFromISO(max);
  304. }
  305. }
  306. // --------------------------------------------------------------------------
  307. //
  308. // Lifecycle
  309. //
  310. // --------------------------------------------------------------------------
  311. connectedCallback() {
  312. if (Array.isArray(this.value)) {
  313. this.valueAsDate = this.value.map((v) => dateFromISO(v));
  314. this.start = this.value[0];
  315. this.end = this.value[1];
  316. }
  317. else if (this.value) {
  318. this.valueAsDate = dateFromISO(this.value);
  319. }
  320. if (this.start) {
  321. this.setStartAsDate(dateFromISO(this.start));
  322. }
  323. if (this.end) {
  324. this.setEndAsDate(dateFromISO(this.end));
  325. }
  326. if (this.min) {
  327. this.minAsDate = dateFromISO(this.min);
  328. }
  329. if (this.max) {
  330. this.maxAsDate = dateFromISO(this.max);
  331. }
  332. }
  333. async componentWillLoad() {
  334. await this.loadLocaleData();
  335. this.onMinChanged(this.min);
  336. this.onMaxChanged(this.max);
  337. }
  338. render() {
  339. var _a;
  340. const date = dateFromRange(this.range ? this.startAsDate : this.valueAsDate, this.minAsDate, this.maxAsDate);
  341. const activeStartDate = this.range
  342. ? this.getActiveStartDate(date, this.minAsDate, this.maxAsDate)
  343. : this.getActiveDate(date, this.minAsDate, this.maxAsDate);
  344. let activeDate = activeStartDate;
  345. const endDate = this.range
  346. ? dateFromRange(this.endAsDate, this.minAsDate, this.maxAsDate)
  347. : null;
  348. const activeEndDate = this.getActiveEndDate(endDate, this.minAsDate, this.maxAsDate);
  349. if ((this.activeRange === "end" ||
  350. (((_a = this.hoverRange) === null || _a === void 0 ? void 0 : _a.focused) === "end" && (!this.proximitySelectionDisabled || endDate))) &&
  351. activeEndDate) {
  352. activeDate = activeEndDate;
  353. }
  354. if (this.range && this.mostRecentRangeValue) {
  355. activeDate = this.mostRecentRangeValue;
  356. }
  357. const minDate = this.range && this.activeRange
  358. ? this.activeRange === "start"
  359. ? this.minAsDate
  360. : date || this.minAsDate
  361. : this.minAsDate;
  362. const maxDate = this.range && this.activeRange
  363. ? this.activeRange === "start"
  364. ? endDate || this.maxAsDate
  365. : this.maxAsDate
  366. : this.maxAsDate;
  367. return (h(Host, { onBlur: this.reset, onKeyUp: this.keyUpHandler, role: "application" }, this.renderCalendar(activeDate, maxDate, minDate, date, endDate)));
  368. }
  369. valueHandler(value) {
  370. if (Array.isArray(value)) {
  371. this.valueAsDate = value.map((v) => dateFromISO(v));
  372. this.start = value[0];
  373. this.end = value[1];
  374. }
  375. else if (value) {
  376. this.valueAsDate = dateFromISO(value);
  377. this.start = "";
  378. this.end = "";
  379. }
  380. }
  381. startWatcher(start) {
  382. this.setStartAsDate(dateFromISO(start));
  383. }
  384. endWatcher(end) {
  385. this.setEndAsDate(dateFromISO(end));
  386. }
  387. async loadLocaleData() {
  388. if (!Build.isBrowser) {
  389. return;
  390. }
  391. const { locale } = this;
  392. this.localeData = await getLocaleData(locale);
  393. }
  394. /**
  395. * Render calcite-date-picker-month-header and calcite-date-picker-month
  396. */
  397. renderCalendar(activeDate, maxDate, minDate, date, endDate) {
  398. return (this.localeData && [
  399. h("calcite-date-picker-month-header", { activeDate: activeDate, headingLevel: this.headingLevel || HEADING_LEVEL, intlNextMonth: this.intlNextMonth, intlPrevMonth: this.intlPrevMonth, intlYear: this.intlYear, localeData: this.localeData, max: maxDate, min: minDate, onCalciteDatePickerSelect: this.monthHeaderSelectChange, scale: this.scale, selectedDate: this.activeRange === "end" ? endDate : date || new Date() }),
  400. h("calcite-date-picker-month", { activeDate: activeDate, endDate: this.range ? endDate : undefined, hoverRange: this.hoverRange, localeData: this.localeData, max: maxDate, min: minDate, onCalciteDatePickerActiveDateChange: this.monthActiveDateChange, onCalciteDatePickerHover: this.monthHoverChange, onCalciteDatePickerMouseOut: this.monthMouseOutChange, onCalciteDatePickerSelect: this.monthDateChange, scale: this.scale, selectedDate: this.activeRange === "end" ? endDate : date, startDate: this.range ? date : undefined })
  401. ]);
  402. }
  403. /**
  404. * Update date instance of start if valid
  405. */
  406. setStartAsDate(startDate, emit) {
  407. this.startAsDate = startDate;
  408. this.mostRecentRangeValue = this.startAsDate;
  409. if (emit) {
  410. this.calciteDatePickerRangeChange.emit({
  411. startDate,
  412. endDate: this.endAsDate
  413. });
  414. }
  415. }
  416. /**
  417. * Update date instance of end if valid
  418. */
  419. setEndAsDate(endDate, emit) {
  420. this.endAsDate = endDate;
  421. this.mostRecentRangeValue = this.endAsDate;
  422. if (emit) {
  423. this.calciteDatePickerRangeChange.emit({
  424. startDate: this.startAsDate,
  425. endDate
  426. });
  427. }
  428. }
  429. setEndDate(date) {
  430. this.end = date ? dateToISO(date) : "";
  431. this.setEndAsDate(date, true);
  432. this.activeEndDate = date || null;
  433. }
  434. setStartDate(date) {
  435. this.start = date ? dateToISO(date) : "";
  436. this.setStartAsDate(date, true);
  437. this.activeStartDate = date || null;
  438. }
  439. /**
  440. * Get an active date using the value, or current date as default
  441. */
  442. getActiveDate(value, min, max) {
  443. return dateFromRange(this.activeDate, min, max) || value || dateFromRange(new Date(), min, max);
  444. }
  445. getActiveStartDate(value, min, max) {
  446. return (dateFromRange(this.activeStartDate, min, max) || value || dateFromRange(new Date(), min, max));
  447. }
  448. getActiveEndDate(value, min, max) {
  449. return (dateFromRange(this.activeEndDate, min, max) || value || dateFromRange(new Date(), min, max));
  450. }
  451. static get assetsDirs() { return ["assets"]; }
  452. get el() { return this; }
  453. static get watchers() { return {
  454. "valueAsDate": ["handleValueAsDate"],
  455. "startAsDate": ["handleRangeChange"],
  456. "endAsDate": ["handleRangeChange"],
  457. "min": ["onMinChanged"],
  458. "max": ["onMaxChanged"],
  459. "value": ["valueHandler"],
  460. "start": ["startWatcher"],
  461. "end": ["endWatcher"],
  462. "locale": ["loadLocaleData"]
  463. }; }
  464. static get style() { return datePickerCss; }
  465. }, [1, "calcite-date-picker", {
  466. "activeRange": [1, "active-range"],
  467. "value": [1025],
  468. "headingLevel": [2, "heading-level"],
  469. "valueAsDate": [1040],
  470. "startAsDate": [1040],
  471. "endAsDate": [1040],
  472. "minAsDate": [1040],
  473. "maxAsDate": [1040],
  474. "min": [1025],
  475. "max": [1025],
  476. "intlPrevMonth": [1, "intl-prev-month"],
  477. "intlNextMonth": [1, "intl-next-month"],
  478. "intlYear": [1, "intl-year"],
  479. "locale": [1],
  480. "scale": [513],
  481. "range": [516],
  482. "start": [1025],
  483. "end": [1025],
  484. "proximitySelectionDisabled": [4, "proximity-selection-disabled"],
  485. "activeDate": [32],
  486. "activeStartDate": [32],
  487. "activeEndDate": [32],
  488. "localeData": [32],
  489. "hoverRange": [32]
  490. }]);
  491. function defineCustomElement() {
  492. if (typeof customElements === "undefined") {
  493. return;
  494. }
  495. const components = ["calcite-date-picker", "calcite-date-picker-day", "calcite-date-picker-month", "calcite-date-picker-month-header", "calcite-icon"];
  496. components.forEach(tagName => { switch (tagName) {
  497. case "calcite-date-picker":
  498. if (!customElements.get(tagName)) {
  499. customElements.define(tagName, DatePicker);
  500. }
  501. break;
  502. case "calcite-date-picker-day":
  503. if (!customElements.get(tagName)) {
  504. defineCustomElement$4();
  505. }
  506. break;
  507. case "calcite-date-picker-month":
  508. if (!customElements.get(tagName)) {
  509. defineCustomElement$3();
  510. }
  511. break;
  512. case "calcite-date-picker-month-header":
  513. if (!customElements.get(tagName)) {
  514. defineCustomElement$2();
  515. }
  516. break;
  517. case "calcite-icon":
  518. if (!customElements.get(tagName)) {
  519. defineCustomElement$1();
  520. }
  521. break;
  522. } });
  523. }
  524. defineCustomElement();
  525. export { DatePicker as D, TEXT as T, defineCustomElement as d, getLocaleData as g };