date-picker.js 19 KB

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