ion-datetime.js 110 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { proxyCustomElement, HTMLElement, createEvent, writeTask, h, Host } from '@stencil/core/internal/client';
  5. import { startFocusVisible } from './focus-visible.js';
  6. import { r as raf, e as renderHiddenInput, g as getElementRoot } from './helpers.js';
  7. import { p as printIonError, a as printIonWarning } from './index4.js';
  8. import { i as isRTL } from './dir.js';
  9. import { c as createColorClasses } from './theme.js';
  10. import { a as chevronBack, f as chevronForward, c as chevronDown, g as caretUpSharp, h as caretDownSharp } from './index7.js';
  11. import { b as getIonMode } from './ionic-global.js';
  12. import { g as generateDayAriaLabel, a as getDay, i as isBefore, b as isAfter, c as isSameDay, d as getPreviousMonth, e as getNextMonth, v as validateParts, f as getPartsFromCalendarDay, h as getNextYear, j as getPreviousYear, k as getEndOfWeek, l as getStartOfWeek, m as getPreviousDay, n as getNextDay, o as getPreviousWeek, p as getNextWeek, q as parseMinParts, r as parseMaxParts, s as parseDate, w as warnIfValueOutOfBounds, t as convertToArrayOfNumbers, u as convertDataToISO, x as getToday, y as getClosestValidDate, z as generateMonths, A as getNumDaysInMonth, B as getCombinedDateColumnData, C as getMonthColumnData, D as getDayColumnData, E as getYearColumnData, F as isMonthFirstLocale, G as getTimeColumnsData, H as isLocaleDayPeriodRTL, I as getMonthAndYear, J as getDaysOfWeek, K as getDaysOfMonth, L as getHourCycle, M as getLocalizedTime, N as getLocalizedDateTime, O as formatValue, P as clampDate, Q as parseAmPm, R as calculateHourFromAMPM } from './data.js';
  13. import { d as defineCustomElement$a } from './backdrop.js';
  14. import { d as defineCustomElement$9 } from './button.js';
  15. import { d as defineCustomElement$8 } from './buttons.js';
  16. import { d as defineCustomElement$7 } from './icon.js';
  17. import { d as defineCustomElement$6 } from './picker.js';
  18. import { d as defineCustomElement$5 } from './picker-column.js';
  19. import { d as defineCustomElement$4 } from './picker-column-option.js';
  20. import { d as defineCustomElement$3 } from './popover.js';
  21. import { d as defineCustomElement$2 } from './ripple-effect.js';
  22. const isYearDisabled = (refYear, minParts, maxParts) => {
  23. if (minParts && minParts.year > refYear) {
  24. return true;
  25. }
  26. if (maxParts && maxParts.year < refYear) {
  27. return true;
  28. }
  29. return false;
  30. };
  31. /**
  32. * Returns true if a given day should
  33. * not be interactive according to its value,
  34. * or the max/min dates.
  35. */
  36. const isDayDisabled = (refParts, minParts, maxParts, dayValues) => {
  37. /**
  38. * If this is a filler date (i.e. padding)
  39. * then the date is disabled.
  40. */
  41. if (refParts.day === null) {
  42. return true;
  43. }
  44. /**
  45. * If user passed in a list of acceptable day values
  46. * check to make sure that the date we are looking
  47. * at is in this array.
  48. */
  49. if (dayValues !== undefined && !dayValues.includes(refParts.day)) {
  50. return true;
  51. }
  52. /**
  53. * Given a min date, perform the following
  54. * checks. If any of them are true, then the
  55. * day should be disabled:
  56. * 1. Is the current year < the min allowed year?
  57. * 2. Is the current year === min allowed year,
  58. * but the current month < the min allowed month?
  59. * 3. Is the current year === min allowed year, the
  60. * current month === min allow month, but the current
  61. * day < the min allowed day?
  62. */
  63. if (minParts && isBefore(refParts, minParts)) {
  64. return true;
  65. }
  66. /**
  67. * Given a max date, perform the following
  68. * checks. If any of them are true, then the
  69. * day should be disabled:
  70. * 1. Is the current year > the max allowed year?
  71. * 2. Is the current year === max allowed year,
  72. * but the current month > the max allowed month?
  73. * 3. Is the current year === max allowed year, the
  74. * current month === max allow month, but the current
  75. * day > the max allowed day?
  76. */
  77. if (maxParts && isAfter(refParts, maxParts)) {
  78. return true;
  79. }
  80. /**
  81. * If none of these checks
  82. * passed then the date should
  83. * be interactive.
  84. */
  85. return false;
  86. };
  87. /**
  88. * Given a locale, a date, the selected date(s), and today's date,
  89. * generate the state for a given calendar day button.
  90. */
  91. const getCalendarDayState = (locale, refParts, activeParts, todayParts, minParts, maxParts, dayValues) => {
  92. /**
  93. * activeParts signals what day(s) are currently selected in the datetime.
  94. * If multiple="true", this will be an array, but the logic in this util
  95. * is the same whether we have one selected day or many because we're only
  96. * calculating the state for one button. So, we treat a single activeParts value
  97. * the same as an array of length one.
  98. */
  99. const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
  100. /**
  101. * The day button is active if it is selected, or in other words, if refParts
  102. * matches at least one selected date.
  103. */
  104. const isActive = activePartsArray.find((parts) => isSameDay(refParts, parts)) !== undefined;
  105. const isToday = isSameDay(refParts, todayParts);
  106. const disabled = isDayDisabled(refParts, minParts, maxParts, dayValues);
  107. /**
  108. * Note that we always return one object regardless of whether activeParts
  109. * was an array, since we pare down to one value for isActive.
  110. */
  111. return {
  112. disabled,
  113. isActive,
  114. isToday,
  115. ariaSelected: isActive ? 'true' : null,
  116. ariaLabel: generateDayAriaLabel(locale, isToday, refParts),
  117. text: refParts.day != null ? getDay(locale, refParts) : null,
  118. };
  119. };
  120. /**
  121. * Returns `true` if the month is disabled given the
  122. * current date value and min/max date constraints.
  123. */
  124. const isMonthDisabled = (refParts, { minParts, maxParts, }) => {
  125. // If the year is disabled then the month is disabled.
  126. if (isYearDisabled(refParts.year, minParts, maxParts)) {
  127. return true;
  128. }
  129. // If the date value is before the min date, then the month is disabled.
  130. // If the date value is after the max date, then the month is disabled.
  131. if ((minParts && isBefore(refParts, minParts)) || (maxParts && isAfter(refParts, maxParts))) {
  132. return true;
  133. }
  134. return false;
  135. };
  136. /**
  137. * Given a working date, an optional minimum date range,
  138. * and an optional maximum date range; determine if the
  139. * previous navigation button is disabled.
  140. */
  141. const isPrevMonthDisabled = (refParts, minParts, maxParts) => {
  142. const prevMonth = Object.assign(Object.assign({}, getPreviousMonth(refParts)), { day: null });
  143. return isMonthDisabled(prevMonth, {
  144. minParts,
  145. maxParts,
  146. });
  147. };
  148. /**
  149. * Given a working date and a maximum date range,
  150. * determine if the next navigation button is disabled.
  151. */
  152. const isNextMonthDisabled = (refParts, maxParts) => {
  153. const nextMonth = Object.assign(Object.assign({}, getNextMonth(refParts)), { day: null });
  154. return isMonthDisabled(nextMonth, {
  155. maxParts,
  156. });
  157. };
  158. /**
  159. * Given the value of the highlightedDates property
  160. * and an ISO string, return the styles to use for
  161. * that date, or undefined if none are found.
  162. */
  163. const getHighlightStyles = (highlightedDates, dateIsoString, el) => {
  164. if (Array.isArray(highlightedDates)) {
  165. const dateStringWithoutTime = dateIsoString.split('T')[0];
  166. const matchingHighlight = highlightedDates.find((hd) => hd.date === dateStringWithoutTime);
  167. if (matchingHighlight) {
  168. return {
  169. textColor: matchingHighlight.textColor,
  170. backgroundColor: matchingHighlight.backgroundColor,
  171. };
  172. }
  173. }
  174. else {
  175. /**
  176. * Wrap in a try-catch to prevent exceptions in the user's function
  177. * from interrupting the calendar's rendering.
  178. */
  179. try {
  180. return highlightedDates(dateIsoString);
  181. }
  182. catch (e) {
  183. printIonError('[ion-datetime] - Exception thrown from provided `highlightedDates` callback. Please check your function and try again.', el, e);
  184. }
  185. }
  186. return undefined;
  187. };
  188. /**
  189. * If a time zone is provided in the format options, the rendered text could
  190. * differ from what was selected in the Datetime, which could cause
  191. * confusion.
  192. */
  193. const warnIfTimeZoneProvided = (el, formatOptions) => {
  194. var _a, _b, _c, _d;
  195. if (((_a = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) === null || _a === void 0 ? void 0 : _a.timeZone) ||
  196. ((_b = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) === null || _b === void 0 ? void 0 : _b.timeZoneName) ||
  197. ((_c = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) === null || _c === void 0 ? void 0 : _c.timeZone) ||
  198. ((_d = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) === null || _d === void 0 ? void 0 : _d.timeZoneName)) {
  199. printIonWarning('[ion-datetime] - "timeZone" and "timeZoneName" are not supported in "formatOptions".', el);
  200. }
  201. };
  202. const checkForPresentationFormatMismatch = (el, presentation, formatOptions) => {
  203. // formatOptions is not required
  204. if (!formatOptions)
  205. return;
  206. // If formatOptions is provided, the date and/or time objects are required, depending on the presentation
  207. switch (presentation) {
  208. case 'date':
  209. case 'month-year':
  210. case 'month':
  211. case 'year':
  212. if (formatOptions.date === undefined) {
  213. printIonWarning(`[ion-datetime] - The '${presentation}' presentation requires a date object in formatOptions.`, el);
  214. }
  215. break;
  216. case 'time':
  217. if (formatOptions.time === undefined) {
  218. printIonWarning(`[ion-datetime] - The 'time' presentation requires a time object in formatOptions.`, el);
  219. }
  220. break;
  221. case 'date-time':
  222. case 'time-date':
  223. if (formatOptions.date === undefined && formatOptions.time === undefined) {
  224. printIonWarning(`[ion-datetime] - The '${presentation}' presentation requires either a date or time object (or both) in formatOptions.`, el);
  225. }
  226. break;
  227. }
  228. };
  229. const datetimeIosCss = ":host{display:-ms-flexbox;display:flex;-ms-flex-flow:column;flex-flow:column;background:var(--background);overflow:hidden}:host(.datetime-size-fixed){width:auto;height:auto}:host(.datetime-size-fixed:not(.datetime-prefer-wheel)){max-width:350px}:host(.datetime-size-fixed.datetime-prefer-wheel){min-width:350px;max-width:-webkit-max-content;max-width:-moz-max-content;max-width:max-content}:host(.datetime-size-cover){width:100%}:host .calendar-body,:host .datetime-year{opacity:0}:host(:not(.datetime-ready)) .datetime-year{position:absolute;pointer-events:none}:host(.datetime-ready) .calendar-body{opacity:1}:host(.datetime-ready) .datetime-year{display:none;opacity:1}:host .wheel-order-year-first .day-column{-ms-flex-order:3;order:3;text-align:end}:host .wheel-order-year-first .month-column{-ms-flex-order:2;order:2;text-align:end}:host .wheel-order-year-first .year-column{-ms-flex-order:1;order:1;text-align:start}:host .datetime-calendar,:host .datetime-year{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;-ms-flex-flow:column;flex-flow:column}:host(.show-month-and-year) .datetime-year{display:-ms-flexbox;display:flex}:host(.show-month-and-year) .calendar-next-prev,:host(.show-month-and-year) .calendar-days-of-week,:host(.show-month-and-year) .calendar-body,:host(.show-month-and-year) .datetime-time{display:none}:host(.month-year-picker-open) .datetime-footer{display:none}:host(.datetime-disabled){pointer-events:none}:host(.datetime-disabled) .calendar-days-of-week,:host(.datetime-disabled) .datetime-time{opacity:0.4}:host(.datetime-readonly){pointer-events:none;}:host(.datetime-readonly) .calendar-action-buttons,:host(.datetime-readonly) .calendar-body,:host(.datetime-readonly) .datetime-year{pointer-events:initial}:host(.datetime-readonly) .calendar-day[disabled]:not(.calendar-day-constrained),:host(.datetime-readonly) .datetime-action-buttons ion-button[disabled]{opacity:1}:host .datetime-header .datetime-title{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}:host .datetime-action-buttons.has-clear-button{width:100%}:host .datetime-action-buttons ion-buttons{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}.datetime-action-buttons .datetime-action-buttons-container{display:-ms-flexbox;display:flex}:host .calendar-action-buttons{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}:host .calendar-action-buttons ion-button{--background:transparent}:host .calendar-days-of-week{display:grid;grid-template-columns:repeat(7, 1fr);text-align:center}.calendar-days-of-week .day-of-week{-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;margin-top:0;margin-bottom:0}:host .calendar-body{display:-ms-flexbox;display:flex;-ms-flex-positive:1;flex-grow:1;-webkit-scroll-snap-type:x mandatory;-ms-scroll-snap-type:x mandatory;scroll-snap-type:x mandatory;overflow-x:scroll;overflow-y:hidden;scrollbar-width:none;outline:none}:host .calendar-body .calendar-month{display:-ms-flexbox;display:flex;-ms-flex-flow:column;flex-flow:column;scroll-snap-align:start;scroll-snap-stop:always;-ms-flex-negative:0;flex-shrink:0;width:100%}:host .calendar-body .calendar-month-disabled{scroll-snap-align:none}:host .calendar-body::-webkit-scrollbar{display:none}:host .calendar-body .calendar-month-grid{display:grid;grid-template-columns:repeat(7, 1fr)}:host .calendar-day-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;min-width:0;min-height:0;overflow:visible}.calendar-day{border-radius:50%;-webkit-padding-start:0px;padding-inline-start:0px;-webkit-padding-end:0px;padding-inline-end:0px;padding-top:0px;padding-bottom:0px;-webkit-margin-start:0px;margin-inline-start:0px;-webkit-margin-end:0px;margin-inline-end:0px;margin-top:0px;margin-bottom:0px;display:-ms-flexbox;display:flex;position:relative;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;border:none;outline:none;background:none;color:currentColor;font-family:var(--ion-font-family, inherit);cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;z-index:0}:host .calendar-day[disabled]{pointer-events:none;opacity:0.4}.calendar-day:focus{background:rgba(var(--ion-color-base-rgb), 0.2);-webkit-box-shadow:0px 0px 0px 4px rgba(var(--ion-color-base-rgb), 0.2);box-shadow:0px 0px 0px 4px rgba(var(--ion-color-base-rgb), 0.2)}:host .datetime-time{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}:host(.datetime-presentation-time) .datetime-time{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0}:host ion-popover{--height:200px}:host .time-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}:host .time-body{border-radius:8px;-webkit-padding-start:12px;padding-inline-start:12px;-webkit-padding-end:12px;padding-inline-end:12px;padding-top:6px;padding-bottom:6px;display:-ms-flexbox;display:flex;border:none;background:var(--ion-color-step-300, var(--ion-background-color-step-300, #edeef0));color:var(--ion-text-color, #000);font-family:inherit;font-size:inherit;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}:host .time-body-active{color:var(--ion-color-base)}:host(.in-item){position:static}:host(.show-month-and-year) .calendar-action-buttons .calendar-month-year-toggle{color:var(--ion-color-base)}.calendar-month-year{min-width:0}.calendar-month-year-toggle{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;text-decoration:inherit;text-indent:inherit;text-overflow:inherit;text-transform:inherit;text-align:inherit;white-space:inherit;color:inherit;position:relative;border:0;outline:none;background:transparent;cursor:pointer;z-index:1}.calendar-month-year-toggle::after{left:0;right:0;top:0;bottom:0;position:absolute;content:\"\";opacity:0;-webkit-transition:opacity 15ms linear, background-color 15ms linear;transition:opacity 15ms linear, background-color 15ms linear;z-index:-1}.calendar-month-year-toggle.ion-focused::after{background:currentColor}.calendar-month-year-toggle:disabled{opacity:0.3;pointer-events:none}.calendar-month-year-toggle ion-icon{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:0;padding-inline-end:0;padding-top:0;padding-bottom:0;-ms-flex-negative:0;flex-shrink:0}.calendar-month-year-toggle #toggle-wrapper{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center}ion-picker{--highlight-background:var(--wheel-highlight-background);--highlight-border-radius:var(--wheel-highlight-border-radius);--fade-background-rgb:var(--wheel-fade-background-rgb)}:host{--background:var(--ion-color-light, #f4f5f8);--background-rgb:var(--ion-color-light-rgb, 244, 245, 248);--title-color:var(--ion-color-step-600, var(--ion-text-color-step-400, #666666))}:host(.datetime-presentation-date-time:not(.datetime-prefer-wheel)),:host(.datetime-presentation-time-date:not(.datetime-prefer-wheel)),:host(.datetime-presentation-date:not(.datetime-prefer-wheel)){min-height:350px}:host .datetime-header{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px;padding-top:16px;padding-bottom:16px;border-bottom:0.55px solid var(--ion-color-step-200, var(--ion-background-color-step-200, #cccccc));font-size:min(0.875rem, 22.4px)}:host .datetime-header .datetime-title{color:var(--title-color)}:host .datetime-header .datetime-selected-date{margin-top:10px}.calendar-month-year-toggle{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px;padding-top:0px;padding-bottom:0px;min-height:44px;font-size:min(1rem, 25.6px);font-weight:600}.calendar-month-year-toggle.ion-focused::after{opacity:0.15}.calendar-month-year-toggle #toggle-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:8px;margin-inline-end:8px;margin-top:10px;margin-bottom:10px}:host .calendar-action-buttons .calendar-month-year-toggle ion-icon,:host .calendar-action-buttons ion-buttons ion-button{color:var(--ion-color-base)}:host .calendar-action-buttons ion-buttons{padding-left:0;padding-right:0;padding-top:8px;padding-bottom:0}:host .calendar-action-buttons ion-buttons ion-button{margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}:host .calendar-days-of-week{-webkit-padding-start:8px;padding-inline-start:8px;-webkit-padding-end:8px;padding-inline-end:8px;padding-top:0;padding-bottom:0;color:var(--ion-color-step-300, var(--ion-text-color-step-700, #b3b3b3));font-size:min(0.75rem, 19.2px);font-weight:600;line-height:24px;text-transform:uppercase}@supports (border-radius: mod(1px, 1px)){.calendar-days-of-week .day-of-week{width:clamp(20px, calc(mod(min(1rem, 24px), 24px) * 10), 100%);height:24px;overflow:hidden}.calendar-day{border-radius:max(8px, mod(min(1rem, 24px), 24px) * 10)}}@supports ((border-radius: mod(1px, 1px)) and (background: -webkit-named-image(apple-pay-logo-black)) and (not (contain-intrinsic-size: none))) or (not (border-radius: mod(1px, 1px))){.calendar-days-of-week .day-of-week{width:auto;height:auto;overflow:initial}.calendar-day{border-radius:32px}}:host .calendar-body .calendar-month .calendar-month-grid{-webkit-padding-start:8px;padding-inline-start:8px;-webkit-padding-end:8px;padding-inline-end:8px;padding-top:8px;padding-bottom:8px;-ms-flex-align:center;align-items:center;height:calc(100% - 16px)}:host .calendar-day-wrapper{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-top:4px;padding-bottom:4px;height:0;min-height:1rem}:host .calendar-day{width:40px;min-width:40px;height:40px;font-size:min(1.25rem, 32px)}.calendar-day.calendar-day-active{background:rgba(var(--ion-color-base-rgb), 0.2);font-size:min(1.375rem, 35.2px)}:host .calendar-day.calendar-day-today{color:var(--ion-color-base)}:host .calendar-day.calendar-day-active{color:var(--ion-color-base);font-weight:600}:host .calendar-day.calendar-day-today.calendar-day-active{background:var(--ion-color-base);color:var(--ion-color-contrast)}:host .datetime-time{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px;padding-top:8px;padding-bottom:16px;font-size:min(1rem, 25.6px)}:host .datetime-time .time-header{font-weight:600}:host .datetime-buttons{-webkit-padding-start:8px;padding-inline-start:8px;-webkit-padding-end:8px;padding-inline-end:8px;padding-top:8px;padding-bottom:8px;border-top:0.55px solid var(--ion-color-step-200, var(--ion-background-color-step-200, #cccccc))}:host .datetime-buttons ::slotted(ion-buttons),:host .datetime-buttons ion-buttons{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}:host .datetime-action-buttons{width:100%}";
  230. const IonDatetimeIosStyle0 = datetimeIosCss;
  231. const datetimeMdCss = ":host{display:-ms-flexbox;display:flex;-ms-flex-flow:column;flex-flow:column;background:var(--background);overflow:hidden}:host(.datetime-size-fixed){width:auto;height:auto}:host(.datetime-size-fixed:not(.datetime-prefer-wheel)){max-width:350px}:host(.datetime-size-fixed.datetime-prefer-wheel){min-width:350px;max-width:-webkit-max-content;max-width:-moz-max-content;max-width:max-content}:host(.datetime-size-cover){width:100%}:host .calendar-body,:host .datetime-year{opacity:0}:host(:not(.datetime-ready)) .datetime-year{position:absolute;pointer-events:none}:host(.datetime-ready) .calendar-body{opacity:1}:host(.datetime-ready) .datetime-year{display:none;opacity:1}:host .wheel-order-year-first .day-column{-ms-flex-order:3;order:3;text-align:end}:host .wheel-order-year-first .month-column{-ms-flex-order:2;order:2;text-align:end}:host .wheel-order-year-first .year-column{-ms-flex-order:1;order:1;text-align:start}:host .datetime-calendar,:host .datetime-year{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;-ms-flex-flow:column;flex-flow:column}:host(.show-month-and-year) .datetime-year{display:-ms-flexbox;display:flex}:host(.show-month-and-year) .calendar-next-prev,:host(.show-month-and-year) .calendar-days-of-week,:host(.show-month-and-year) .calendar-body,:host(.show-month-and-year) .datetime-time{display:none}:host(.month-year-picker-open) .datetime-footer{display:none}:host(.datetime-disabled){pointer-events:none}:host(.datetime-disabled) .calendar-days-of-week,:host(.datetime-disabled) .datetime-time{opacity:0.4}:host(.datetime-readonly){pointer-events:none;}:host(.datetime-readonly) .calendar-action-buttons,:host(.datetime-readonly) .calendar-body,:host(.datetime-readonly) .datetime-year{pointer-events:initial}:host(.datetime-readonly) .calendar-day[disabled]:not(.calendar-day-constrained),:host(.datetime-readonly) .datetime-action-buttons ion-button[disabled]{opacity:1}:host .datetime-header .datetime-title{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}:host .datetime-action-buttons.has-clear-button{width:100%}:host .datetime-action-buttons ion-buttons{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}.datetime-action-buttons .datetime-action-buttons-container{display:-ms-flexbox;display:flex}:host .calendar-action-buttons{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}:host .calendar-action-buttons ion-button{--background:transparent}:host .calendar-days-of-week{display:grid;grid-template-columns:repeat(7, 1fr);text-align:center}.calendar-days-of-week .day-of-week{-webkit-margin-start:auto;margin-inline-start:auto;-webkit-margin-end:auto;margin-inline-end:auto;margin-top:0;margin-bottom:0}:host .calendar-body{display:-ms-flexbox;display:flex;-ms-flex-positive:1;flex-grow:1;-webkit-scroll-snap-type:x mandatory;-ms-scroll-snap-type:x mandatory;scroll-snap-type:x mandatory;overflow-x:scroll;overflow-y:hidden;scrollbar-width:none;outline:none}:host .calendar-body .calendar-month{display:-ms-flexbox;display:flex;-ms-flex-flow:column;flex-flow:column;scroll-snap-align:start;scroll-snap-stop:always;-ms-flex-negative:0;flex-shrink:0;width:100%}:host .calendar-body .calendar-month-disabled{scroll-snap-align:none}:host .calendar-body::-webkit-scrollbar{display:none}:host .calendar-body .calendar-month-grid{display:grid;grid-template-columns:repeat(7, 1fr)}:host .calendar-day-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;min-width:0;min-height:0;overflow:visible}.calendar-day{border-radius:50%;-webkit-padding-start:0px;padding-inline-start:0px;-webkit-padding-end:0px;padding-inline-end:0px;padding-top:0px;padding-bottom:0px;-webkit-margin-start:0px;margin-inline-start:0px;-webkit-margin-end:0px;margin-inline-end:0px;margin-top:0px;margin-bottom:0px;display:-ms-flexbox;display:flex;position:relative;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;border:none;outline:none;background:none;color:currentColor;font-family:var(--ion-font-family, inherit);cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;z-index:0}:host .calendar-day[disabled]{pointer-events:none;opacity:0.4}.calendar-day:focus{background:rgba(var(--ion-color-base-rgb), 0.2);-webkit-box-shadow:0px 0px 0px 4px rgba(var(--ion-color-base-rgb), 0.2);box-shadow:0px 0px 0px 4px rgba(var(--ion-color-base-rgb), 0.2)}:host .datetime-time{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}:host(.datetime-presentation-time) .datetime-time{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0}:host ion-popover{--height:200px}:host .time-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}:host .time-body{border-radius:8px;-webkit-padding-start:12px;padding-inline-start:12px;-webkit-padding-end:12px;padding-inline-end:12px;padding-top:6px;padding-bottom:6px;display:-ms-flexbox;display:flex;border:none;background:var(--ion-color-step-300, var(--ion-background-color-step-300, #edeef0));color:var(--ion-text-color, #000);font-family:inherit;font-size:inherit;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}:host .time-body-active{color:var(--ion-color-base)}:host(.in-item){position:static}:host(.show-month-and-year) .calendar-action-buttons .calendar-month-year-toggle{color:var(--ion-color-base)}.calendar-month-year{min-width:0}.calendar-month-year-toggle{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;letter-spacing:inherit;text-decoration:inherit;text-indent:inherit;text-overflow:inherit;text-transform:inherit;text-align:inherit;white-space:inherit;color:inherit;position:relative;border:0;outline:none;background:transparent;cursor:pointer;z-index:1}.calendar-month-year-toggle::after{left:0;right:0;top:0;bottom:0;position:absolute;content:\"\";opacity:0;-webkit-transition:opacity 15ms linear, background-color 15ms linear;transition:opacity 15ms linear, background-color 15ms linear;z-index:-1}.calendar-month-year-toggle.ion-focused::after{background:currentColor}.calendar-month-year-toggle:disabled{opacity:0.3;pointer-events:none}.calendar-month-year-toggle ion-icon{-webkit-padding-start:4px;padding-inline-start:4px;-webkit-padding-end:0;padding-inline-end:0;padding-top:0;padding-bottom:0;-ms-flex-negative:0;flex-shrink:0}.calendar-month-year-toggle #toggle-wrapper{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center}ion-picker{--highlight-background:var(--wheel-highlight-background);--highlight-border-radius:var(--wheel-highlight-border-radius);--fade-background-rgb:var(--wheel-fade-background-rgb)}:host{--background:var(--ion-color-step-100, var(--ion-background-color-step-100, #ffffff));--title-color:var(--ion-color-contrast)}:host .datetime-header{-webkit-padding-start:20px;padding-inline-start:20px;-webkit-padding-end:20px;padding-inline-end:20px;padding-top:20px;padding-bottom:20px;background:var(--ion-color-base);color:var(--title-color)}:host .datetime-header .datetime-title{font-size:0.75rem;text-transform:uppercase}:host .datetime-header .datetime-selected-date{margin-top:30px;font-size:2.125rem}:host .calendar-action-buttons ion-button{--color:var(--ion-color-step-650, var(--ion-text-color-step-350, #595959))}.calendar-month-year-toggle{-webkit-padding-start:20px;padding-inline-start:20px;-webkit-padding-end:16px;padding-inline-end:16px;padding-top:12px;padding-bottom:12px;min-height:48px;background:transparent;color:var(--ion-color-step-650, var(--ion-text-color-step-350, #595959));z-index:1}.calendar-month-year-toggle.ion-focused::after{opacity:0.04}.calendar-month-year-toggle ion-ripple-effect{color:currentColor}@media (any-hover: hover){.calendar-month-year-toggle.ion-activatable:not(.ion-focused):hover::after{background:currentColor;opacity:0.04}}:host .calendar-days-of-week{-webkit-padding-start:10px;padding-inline-start:10px;-webkit-padding-end:10px;padding-inline-end:10px;padding-top:0px;padding-bottom:0px;color:var(--ion-color-step-500, var(--ion-text-color-step-500, gray));font-size:0.875rem;line-height:36px}:host .calendar-body .calendar-month .calendar-month-grid{-webkit-padding-start:10px;padding-inline-start:10px;-webkit-padding-end:10px;padding-inline-end:10px;padding-top:4px;padding-bottom:4px;grid-template-rows:repeat(6, 1fr)}:host .calendar-day{width:42px;min-width:42px;height:42px;font-size:0.875rem}:host .calendar-day.calendar-day-today{border:1px solid var(--ion-color-base);color:var(--ion-color-base)}:host .calendar-day.calendar-day-active{color:var(--ion-color-contrast)}.calendar-day.calendar-day-active{border:1px solid var(--ion-color-base);background:var(--ion-color-base)}:host .datetime-time{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px;padding-top:8px;padding-bottom:8px}:host .time-header{color:var(--ion-color-step-650, var(--ion-text-color-step-350, #595959))}:host(.datetime-presentation-month) .datetime-year,:host(.datetime-presentation-year) .datetime-year,:host(.datetime-presentation-month-year) .datetime-year{margin-top:20px;margin-bottom:20px}:host .datetime-buttons{-webkit-padding-start:10px;padding-inline-start:10px;-webkit-padding-end:10px;padding-inline-end:10px;padding-top:10px;padding-bottom:10px;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end}";
  232. const IonDatetimeMdStyle0 = datetimeMdCss;
  233. const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLElement {
  234. constructor() {
  235. super();
  236. this.__registerHost();
  237. this.__attachShadow();
  238. this.ionCancel = createEvent(this, "ionCancel", 7);
  239. this.ionChange = createEvent(this, "ionChange", 7);
  240. this.ionValueChange = createEvent(this, "ionValueChange", 7);
  241. this.ionFocus = createEvent(this, "ionFocus", 7);
  242. this.ionBlur = createEvent(this, "ionBlur", 7);
  243. this.ionStyle = createEvent(this, "ionStyle", 7);
  244. this.ionRender = createEvent(this, "ionRender", 7);
  245. this.inputId = `ion-dt-${datetimeIds++}`;
  246. this.prevPresentation = null;
  247. this.warnIfIncorrectValueUsage = () => {
  248. const { multiple, value } = this;
  249. if (!multiple && Array.isArray(value)) {
  250. /**
  251. * We do some processing on the `value` array so
  252. * that it looks more like an array when logged to
  253. * the console.
  254. * Example given ['a', 'b']
  255. * Default toString() behavior: a,b
  256. * Custom behavior: ['a', 'b']
  257. */
  258. printIonWarning(`[ion-datetime] - An array of values was passed, but multiple is "false". This is incorrect usage and may result in unexpected behaviors. To dismiss this warning, pass a string to the "value" property when multiple="false".
  259. Value Passed: [${value.map((v) => `'${v}'`).join(', ')}]
  260. `, this.el);
  261. }
  262. };
  263. this.setValue = (value) => {
  264. this.value = value;
  265. this.ionChange.emit({ value });
  266. };
  267. /**
  268. * Returns the DatetimePart interface
  269. * to use when rendering an initial set of
  270. * data. This should be used when rendering an
  271. * interface in an environment where the `value`
  272. * may not be set. This function works
  273. * by returning the first selected date and then
  274. * falling back to defaultParts if no active date
  275. * is selected.
  276. */
  277. this.getActivePartsWithFallback = () => {
  278. var _a;
  279. const { defaultParts } = this;
  280. return (_a = this.getActivePart()) !== null && _a !== void 0 ? _a : defaultParts;
  281. };
  282. this.getActivePart = () => {
  283. const { activeParts } = this;
  284. return Array.isArray(activeParts) ? activeParts[0] : activeParts;
  285. };
  286. this.closeParentOverlay = (role) => {
  287. const popoverOrModal = this.el.closest('ion-modal, ion-popover');
  288. if (popoverOrModal) {
  289. popoverOrModal.dismiss(undefined, role);
  290. }
  291. };
  292. this.setWorkingParts = (parts) => {
  293. this.workingParts = Object.assign({}, parts);
  294. };
  295. this.setActiveParts = (parts, removeDate = false) => {
  296. /** if the datetime component is in readonly mode,
  297. * allow browsing of the calendar without changing
  298. * the set value
  299. */
  300. if (this.readonly) {
  301. return;
  302. }
  303. const { multiple, minParts, maxParts, activeParts } = this;
  304. /**
  305. * When setting the active parts, it is possible
  306. * to set invalid data. For example,
  307. * when updating January 31 to February,
  308. * February 31 does not exist. As a result
  309. * we need to validate the active parts and
  310. * ensure that we are only setting valid dates.
  311. * Additionally, we need to update the working parts
  312. * too in the event that the validated parts are different.
  313. */
  314. const validatedParts = validateParts(parts, minParts, maxParts);
  315. this.setWorkingParts(validatedParts);
  316. if (multiple) {
  317. const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
  318. if (removeDate) {
  319. this.activeParts = activePartsArray.filter((p) => !isSameDay(p, validatedParts));
  320. }
  321. else {
  322. this.activeParts = [...activePartsArray, validatedParts];
  323. }
  324. }
  325. else {
  326. this.activeParts = Object.assign({}, validatedParts);
  327. }
  328. const hasSlottedButtons = this.el.querySelector('[slot="buttons"]') !== null;
  329. if (hasSlottedButtons || this.showDefaultButtons) {
  330. return;
  331. }
  332. this.confirm();
  333. };
  334. this.initializeKeyboardListeners = () => {
  335. const calendarBodyRef = this.calendarBodyRef;
  336. if (!calendarBodyRef) {
  337. return;
  338. }
  339. const root = this.el.shadowRoot;
  340. /**
  341. * Get a reference to the month
  342. * element we are currently viewing.
  343. */
  344. const currentMonth = calendarBodyRef.querySelector('.calendar-month:nth-of-type(2)');
  345. /**
  346. * When focusing the calendar body, we want to pass focus
  347. * to the working day, but other days should
  348. * only be accessible using the arrow keys. Pressing
  349. * Tab should jump between bodies of selectable content.
  350. */
  351. const checkCalendarBodyFocus = (ev) => {
  352. var _a;
  353. const record = ev[0];
  354. /**
  355. * If calendar body was already focused
  356. * when this fired or if the calendar body
  357. * if not currently focused, we should not re-focus
  358. * the inner day.
  359. */
  360. if (((_a = record.oldValue) === null || _a === void 0 ? void 0 : _a.includes('ion-focused')) || !calendarBodyRef.classList.contains('ion-focused')) {
  361. return;
  362. }
  363. this.focusWorkingDay(currentMonth);
  364. };
  365. const mo = new MutationObserver(checkCalendarBodyFocus);
  366. mo.observe(calendarBodyRef, { attributeFilter: ['class'], attributeOldValue: true });
  367. this.destroyKeyboardMO = () => {
  368. mo === null || mo === void 0 ? void 0 : mo.disconnect();
  369. };
  370. /**
  371. * We must use keydown not keyup as we want
  372. * to prevent scrolling when using the arrow keys.
  373. */
  374. calendarBodyRef.addEventListener('keydown', (ev) => {
  375. const activeElement = root.activeElement;
  376. if (!activeElement || !activeElement.classList.contains('calendar-day')) {
  377. return;
  378. }
  379. const parts = getPartsFromCalendarDay(activeElement);
  380. let partsToFocus;
  381. switch (ev.key) {
  382. case 'ArrowDown':
  383. ev.preventDefault();
  384. partsToFocus = getNextWeek(parts);
  385. break;
  386. case 'ArrowUp':
  387. ev.preventDefault();
  388. partsToFocus = getPreviousWeek(parts);
  389. break;
  390. case 'ArrowRight':
  391. ev.preventDefault();
  392. partsToFocus = getNextDay(parts);
  393. break;
  394. case 'ArrowLeft':
  395. ev.preventDefault();
  396. partsToFocus = getPreviousDay(parts);
  397. break;
  398. case 'Home':
  399. ev.preventDefault();
  400. partsToFocus = getStartOfWeek(parts);
  401. break;
  402. case 'End':
  403. ev.preventDefault();
  404. partsToFocus = getEndOfWeek(parts);
  405. break;
  406. case 'PageUp':
  407. ev.preventDefault();
  408. partsToFocus = ev.shiftKey ? getPreviousYear(parts) : getPreviousMonth(parts);
  409. break;
  410. case 'PageDown':
  411. ev.preventDefault();
  412. partsToFocus = ev.shiftKey ? getNextYear(parts) : getNextMonth(parts);
  413. break;
  414. /**
  415. * Do not preventDefault here
  416. * as we do not want to override other
  417. * browser defaults such as pressing Enter/Space
  418. * to select a day.
  419. */
  420. default:
  421. return;
  422. }
  423. /**
  424. * If the day we want to move focus to is
  425. * disabled, do not do anything.
  426. */
  427. if (isDayDisabled(partsToFocus, this.minParts, this.maxParts)) {
  428. return;
  429. }
  430. this.setWorkingParts(Object.assign(Object.assign({}, this.workingParts), partsToFocus));
  431. /**
  432. * Give view a chance to re-render
  433. * then move focus to the new working day
  434. */
  435. requestAnimationFrame(() => this.focusWorkingDay(currentMonth));
  436. });
  437. };
  438. this.focusWorkingDay = (currentMonth) => {
  439. /**
  440. * Get the number of padding days so
  441. * we know how much to offset our next selector by
  442. * to grab the correct calendar-day element.
  443. */
  444. const padding = currentMonth.querySelectorAll('.calendar-day-padding');
  445. const { day } = this.workingParts;
  446. if (day === null) {
  447. return;
  448. }
  449. /**
  450. * Get the calendar day element
  451. * and focus it.
  452. */
  453. const dayEl = currentMonth.querySelector(`.calendar-day-wrapper:nth-of-type(${padding.length + day}) .calendar-day`);
  454. if (dayEl) {
  455. dayEl.focus();
  456. }
  457. };
  458. this.processMinParts = () => {
  459. const { min, defaultParts } = this;
  460. if (min === undefined) {
  461. this.minParts = undefined;
  462. return;
  463. }
  464. this.minParts = parseMinParts(min, defaultParts);
  465. };
  466. this.processMaxParts = () => {
  467. const { max, defaultParts } = this;
  468. if (max === undefined) {
  469. this.maxParts = undefined;
  470. return;
  471. }
  472. this.maxParts = parseMaxParts(max, defaultParts);
  473. };
  474. this.initializeCalendarListener = () => {
  475. const calendarBodyRef = this.calendarBodyRef;
  476. if (!calendarBodyRef) {
  477. return;
  478. }
  479. /**
  480. * For performance reasons, we only render 3
  481. * months at a time: The current month, the previous
  482. * month, and the next month. We have a scroll listener
  483. * on the calendar body to append/prepend new months.
  484. *
  485. * We can do this because Stencil is smart enough to not
  486. * re-create the .calendar-month containers, but rather
  487. * update the content within those containers.
  488. *
  489. * As an added bonus, WebKit has some troubles with
  490. * scroll-snap-stop: always, so not rendering all of
  491. * the months in a row allows us to mostly sidestep
  492. * that issue.
  493. */
  494. const months = calendarBodyRef.querySelectorAll('.calendar-month');
  495. const startMonth = months[0];
  496. const workingMonth = months[1];
  497. const endMonth = months[2];
  498. const mode = getIonMode(this);
  499. const needsiOSRubberBandFix = mode === 'ios' && typeof navigator !== 'undefined' && navigator.maxTouchPoints > 1;
  500. /**
  501. * Before setting up the scroll listener,
  502. * scroll the middle month into view.
  503. * scrollIntoView() will scroll entire page
  504. * if element is not in viewport. Use scrollLeft instead.
  505. */
  506. writeTask(() => {
  507. calendarBodyRef.scrollLeft = startMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
  508. const getChangedMonth = (parts) => {
  509. const box = calendarBodyRef.getBoundingClientRect();
  510. /**
  511. * If the current scroll position is all the way to the left
  512. * then we have scrolled to the previous month.
  513. * Otherwise, assume that we have scrolled to the next
  514. * month. We have a tolerance of 2px to account for
  515. * sub pixel rendering.
  516. *
  517. * Check below the next line ensures that we did not
  518. * swipe and abort (i.e. we swiped but we are still on the current month).
  519. */
  520. const condition = isRTL(this.el) ? calendarBodyRef.scrollLeft >= -2 : calendarBodyRef.scrollLeft <= 2;
  521. const month = condition ? startMonth : endMonth;
  522. /**
  523. * The edge of the month must be lined up with
  524. * the edge of the calendar body in order for
  525. * the component to update. Otherwise, it
  526. * may be the case that the user has paused their
  527. * swipe or the browser has not finished snapping yet.
  528. * Rather than check if the x values are equal,
  529. * we give it a tolerance of 2px to account for
  530. * sub pixel rendering.
  531. */
  532. const monthBox = month.getBoundingClientRect();
  533. if (Math.abs(monthBox.x - box.x) > 2)
  534. return;
  535. /**
  536. * If we're force-rendering a month, assume we've
  537. * scrolled to that and return it.
  538. *
  539. * If forceRenderDate is ever used in a context where the
  540. * forced month is not immediately auto-scrolled to, this
  541. * should be updated to also check whether `month` has the
  542. * same month and year as the forced date.
  543. */
  544. const { forceRenderDate } = this;
  545. if (forceRenderDate !== undefined) {
  546. return { month: forceRenderDate.month, year: forceRenderDate.year, day: forceRenderDate.day };
  547. }
  548. /**
  549. * From here, we can determine if the start
  550. * month or the end month was scrolled into view.
  551. * If no month was changed, then we can return from
  552. * the scroll callback early.
  553. */
  554. if (month === startMonth) {
  555. return getPreviousMonth(parts);
  556. }
  557. else if (month === endMonth) {
  558. return getNextMonth(parts);
  559. }
  560. else {
  561. return;
  562. }
  563. };
  564. const updateActiveMonth = () => {
  565. if (needsiOSRubberBandFix) {
  566. calendarBodyRef.style.removeProperty('pointer-events');
  567. appliediOSRubberBandFix = false;
  568. }
  569. /**
  570. * If the month did not change
  571. * then we can return early.
  572. */
  573. const newDate = getChangedMonth(this.workingParts);
  574. if (!newDate)
  575. return;
  576. const { month, day, year } = newDate;
  577. if (isMonthDisabled({ month, year, day: null }, {
  578. minParts: Object.assign(Object.assign({}, this.minParts), { day: null }),
  579. maxParts: Object.assign(Object.assign({}, this.maxParts), { day: null }),
  580. })) {
  581. return;
  582. }
  583. /**
  584. * Prevent scrolling for other browsers
  585. * to give the DOM time to update and the container
  586. * time to properly snap.
  587. */
  588. calendarBodyRef.style.setProperty('overflow', 'hidden');
  589. /**
  590. * Use a writeTask here to ensure
  591. * that the state is updated and the
  592. * correct month is scrolled into view
  593. * in the same frame. This is not
  594. * typically a problem on newer devices
  595. * but older/slower device may have a flicker
  596. * if we did not do this.
  597. */
  598. writeTask(() => {
  599. this.setWorkingParts(Object.assign(Object.assign({}, this.workingParts), { month, day: day, year }));
  600. calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
  601. calendarBodyRef.style.removeProperty('overflow');
  602. if (this.resolveForceDateScrolling) {
  603. this.resolveForceDateScrolling();
  604. }
  605. });
  606. };
  607. /**
  608. * When the container finishes scrolling we
  609. * need to update the DOM with the selected month.
  610. */
  611. let scrollTimeout;
  612. /**
  613. * We do not want to attempt to set pointer-events
  614. * multiple times within a single swipe gesture as
  615. * that adds unnecessary work to the main thread.
  616. */
  617. let appliediOSRubberBandFix = false;
  618. const scrollCallback = () => {
  619. if (scrollTimeout) {
  620. clearTimeout(scrollTimeout);
  621. }
  622. /**
  623. * On iOS it is possible to quickly rubber band
  624. * the scroll area before the scroll timeout has fired.
  625. * This results in users reaching the end of the scrollable
  626. * container before the DOM has updated.
  627. * By setting `pointer-events: none` we can ensure that
  628. * subsequent swipes do not happen while the container
  629. * is snapping.
  630. */
  631. if (!appliediOSRubberBandFix && needsiOSRubberBandFix) {
  632. calendarBodyRef.style.setProperty('pointer-events', 'none');
  633. appliediOSRubberBandFix = true;
  634. }
  635. // Wait ~3 frames
  636. scrollTimeout = setTimeout(updateActiveMonth, 50);
  637. };
  638. calendarBodyRef.addEventListener('scroll', scrollCallback);
  639. this.destroyCalendarListener = () => {
  640. calendarBodyRef.removeEventListener('scroll', scrollCallback);
  641. };
  642. });
  643. };
  644. /**
  645. * Clean up all listeners except for the overlay
  646. * listener. This is so that we can re-create the listeners
  647. * if the datetime has been hidden/presented by a modal or popover.
  648. */
  649. this.destroyInteractionListeners = () => {
  650. const { destroyCalendarListener, destroyKeyboardMO } = this;
  651. if (destroyCalendarListener !== undefined) {
  652. destroyCalendarListener();
  653. }
  654. if (destroyKeyboardMO !== undefined) {
  655. destroyKeyboardMO();
  656. }
  657. };
  658. this.processValue = (value) => {
  659. const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
  660. const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
  661. const { minParts, maxParts, workingParts, el } = this;
  662. this.warnIfIncorrectValueUsage();
  663. /**
  664. * Return early if the value wasn't parsed correctly, such as
  665. * if an improperly formatted date string was provided.
  666. */
  667. if (!valueToProcess) {
  668. return;
  669. }
  670. /**
  671. * Datetime should only warn of out of bounds values
  672. * if set by the user. If the `value` is undefined,
  673. * we will default to today's date which may be out
  674. * of bounds. In this case, the warning makes it look
  675. * like the developer did something wrong which is
  676. * not true.
  677. */
  678. if (hasValue) {
  679. warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
  680. }
  681. /**
  682. * If there are multiple values, pick an arbitrary one to clamp to. This way,
  683. * if the values are across months, we always show at least one of them. Note
  684. * that the values don't necessarily have to be in order.
  685. */
  686. const singleValue = Array.isArray(valueToProcess) ? valueToProcess[0] : valueToProcess;
  687. const targetValue = clampDate(singleValue, minParts, maxParts);
  688. const { month, day, year, hour, minute } = targetValue;
  689. const ampm = parseAmPm(hour);
  690. /**
  691. * Since `activeParts` indicates a value that
  692. * been explicitly selected either by the
  693. * user or the app, only update `activeParts`
  694. * if the `value` property is set.
  695. */
  696. if (hasValue) {
  697. if (Array.isArray(valueToProcess)) {
  698. this.activeParts = [...valueToProcess];
  699. }
  700. else {
  701. this.activeParts = {
  702. month,
  703. day,
  704. year,
  705. hour,
  706. minute,
  707. ampm,
  708. };
  709. }
  710. }
  711. else {
  712. /**
  713. * Reset the active parts if the value is not set.
  714. * This will clear the selected calendar day when
  715. * performing a clear action or using the reset() method.
  716. */
  717. this.activeParts = [];
  718. }
  719. /**
  720. * Only animate if:
  721. * 1. We're using grid style (wheel style pickers should just jump to new value)
  722. * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
  723. * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
  724. * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
  725. */
  726. const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
  727. const bodyIsVisible = el.classList.contains('datetime-ready');
  728. const { isGridStyle, showMonthAndYear } = this;
  729. let areAllSelectedDatesInSameMonth = true;
  730. if (Array.isArray(valueToProcess)) {
  731. const firstMonth = valueToProcess[0].month;
  732. for (const date of valueToProcess) {
  733. if (date.month !== firstMonth) {
  734. areAllSelectedDatesInSameMonth = false;
  735. break;
  736. }
  737. }
  738. }
  739. /**
  740. * If there is more than one date selected
  741. * and the dates aren't all in the same month,
  742. * then we should neither animate to the date
  743. * nor update the working parts because we do
  744. * not know which date the user wants to view.
  745. */
  746. if (areAllSelectedDatesInSameMonth) {
  747. if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
  748. this.animateToDate(targetValue);
  749. }
  750. else {
  751. /**
  752. * We only need to do this if we didn't just animate to a new month,
  753. * since that calls prevMonth/nextMonth which calls setWorkingParts for us.
  754. */
  755. this.setWorkingParts({
  756. month,
  757. day,
  758. year,
  759. hour,
  760. minute,
  761. ampm,
  762. });
  763. }
  764. }
  765. };
  766. this.animateToDate = async (targetValue) => {
  767. const { workingParts } = this;
  768. /**
  769. * Tell other render functions that we need to force the
  770. * target month to appear in place of the actual next/prev month.
  771. * Because this is a State variable, a rerender will be triggered
  772. * automatically, updating the rendered months.
  773. */
  774. this.forceRenderDate = targetValue;
  775. /**
  776. * Flag that we've started scrolling to the forced date.
  777. * The resolve function will be called by the datetime's
  778. * scroll listener when it's done updating everything.
  779. * This is a replacement for making prev/nextMonth async,
  780. * since the logic we're waiting on is in a listener.
  781. */
  782. const forceDateScrollingPromise = new Promise((resolve) => {
  783. this.resolveForceDateScrolling = resolve;
  784. });
  785. /**
  786. * Animate smoothly to the forced month. This will also update
  787. * workingParts and correct the surrounding months for us.
  788. */
  789. const targetMonthIsBefore = isBefore(targetValue, workingParts);
  790. targetMonthIsBefore ? this.prevMonth() : this.nextMonth();
  791. await forceDateScrollingPromise;
  792. this.resolveForceDateScrolling = undefined;
  793. this.forceRenderDate = undefined;
  794. };
  795. this.onFocus = () => {
  796. this.ionFocus.emit();
  797. };
  798. this.onBlur = () => {
  799. this.ionBlur.emit();
  800. };
  801. this.hasValue = () => {
  802. return this.value != null;
  803. };
  804. this.nextMonth = () => {
  805. const calendarBodyRef = this.calendarBodyRef;
  806. if (!calendarBodyRef) {
  807. return;
  808. }
  809. const nextMonth = calendarBodyRef.querySelector('.calendar-month:last-of-type');
  810. if (!nextMonth) {
  811. return;
  812. }
  813. const left = nextMonth.offsetWidth * 2;
  814. calendarBodyRef.scrollTo({
  815. top: 0,
  816. left: left * (isRTL(this.el) ? -1 : 1),
  817. behavior: 'smooth',
  818. });
  819. };
  820. this.prevMonth = () => {
  821. const calendarBodyRef = this.calendarBodyRef;
  822. if (!calendarBodyRef) {
  823. return;
  824. }
  825. const prevMonth = calendarBodyRef.querySelector('.calendar-month:first-of-type');
  826. if (!prevMonth) {
  827. return;
  828. }
  829. calendarBodyRef.scrollTo({
  830. top: 0,
  831. left: 0,
  832. behavior: 'smooth',
  833. });
  834. };
  835. this.toggleMonthAndYearView = () => {
  836. this.showMonthAndYear = !this.showMonthAndYear;
  837. };
  838. this.showMonthAndYear = false;
  839. this.activeParts = [];
  840. this.workingParts = {
  841. month: 5,
  842. day: 28,
  843. year: 2021,
  844. hour: 13,
  845. minute: 52,
  846. ampm: 'pm',
  847. };
  848. this.isTimePopoverOpen = false;
  849. this.forceRenderDate = undefined;
  850. this.color = 'primary';
  851. this.name = this.inputId;
  852. this.disabled = false;
  853. this.formatOptions = undefined;
  854. this.readonly = false;
  855. this.isDateEnabled = undefined;
  856. this.min = undefined;
  857. this.max = undefined;
  858. this.presentation = 'date-time';
  859. this.cancelText = 'Cancel';
  860. this.doneText = 'Done';
  861. this.clearText = 'Clear';
  862. this.yearValues = undefined;
  863. this.monthValues = undefined;
  864. this.dayValues = undefined;
  865. this.hourValues = undefined;
  866. this.minuteValues = undefined;
  867. this.locale = 'default';
  868. this.firstDayOfWeek = 0;
  869. this.titleSelectedDatesFormatter = undefined;
  870. this.multiple = false;
  871. this.highlightedDates = undefined;
  872. this.value = undefined;
  873. this.showDefaultTitle = false;
  874. this.showDefaultButtons = false;
  875. this.showClearButton = false;
  876. this.showDefaultTimeLabel = true;
  877. this.hourCycle = undefined;
  878. this.size = 'fixed';
  879. this.preferWheel = false;
  880. }
  881. formatOptionsChanged() {
  882. const { el, formatOptions, presentation } = this;
  883. checkForPresentationFormatMismatch(el, presentation, formatOptions);
  884. warnIfTimeZoneProvided(el, formatOptions);
  885. }
  886. disabledChanged() {
  887. this.emitStyle();
  888. }
  889. minChanged() {
  890. this.processMinParts();
  891. }
  892. maxChanged() {
  893. this.processMaxParts();
  894. }
  895. presentationChanged() {
  896. const { el, formatOptions, presentation } = this;
  897. checkForPresentationFormatMismatch(el, presentation, formatOptions);
  898. }
  899. get isGridStyle() {
  900. const { presentation, preferWheel } = this;
  901. const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
  902. return hasDatePresentation && !preferWheel;
  903. }
  904. yearValuesChanged() {
  905. this.parsedYearValues = convertToArrayOfNumbers(this.yearValues);
  906. }
  907. monthValuesChanged() {
  908. this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues);
  909. }
  910. dayValuesChanged() {
  911. this.parsedDayValues = convertToArrayOfNumbers(this.dayValues);
  912. }
  913. hourValuesChanged() {
  914. this.parsedHourValues = convertToArrayOfNumbers(this.hourValues);
  915. }
  916. minuteValuesChanged() {
  917. this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues);
  918. }
  919. /**
  920. * Update the datetime value when the value changes
  921. */
  922. async valueChanged() {
  923. const { value } = this;
  924. if (this.hasValue()) {
  925. this.processValue(value);
  926. }
  927. this.emitStyle();
  928. this.ionValueChange.emit({ value });
  929. }
  930. /**
  931. * Confirms the selected datetime value, updates the
  932. * `value` property, and optionally closes the popover
  933. * or modal that the datetime was presented in.
  934. */
  935. async confirm(closeOverlay = false) {
  936. const { isCalendarPicker, activeParts, preferWheel, workingParts } = this;
  937. /**
  938. * We only update the value if the presentation is not a calendar picker.
  939. */
  940. if (activeParts !== undefined || !isCalendarPicker) {
  941. const activePartsIsArray = Array.isArray(activeParts);
  942. if (activePartsIsArray && activeParts.length === 0) {
  943. if (preferWheel) {
  944. /**
  945. * If the datetime is using a wheel picker, but the
  946. * active parts are empty, then the user has confirmed the
  947. * initial value (working parts) presented to them.
  948. */
  949. this.setValue(convertDataToISO(workingParts));
  950. }
  951. else {
  952. this.setValue(undefined);
  953. }
  954. }
  955. else {
  956. this.setValue(convertDataToISO(activeParts));
  957. }
  958. }
  959. if (closeOverlay) {
  960. this.closeParentOverlay(CONFIRM_ROLE);
  961. }
  962. }
  963. /**
  964. * Resets the internal state of the datetime but does not update the value.
  965. * Passing a valid ISO-8601 string will reset the state of the component to the provided date.
  966. * If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
  967. */
  968. async reset(startDate) {
  969. this.processValue(startDate);
  970. }
  971. /**
  972. * Emits the ionCancel event and
  973. * optionally closes the popover
  974. * or modal that the datetime was
  975. * presented in.
  976. */
  977. async cancel(closeOverlay = false) {
  978. this.ionCancel.emit();
  979. if (closeOverlay) {
  980. this.closeParentOverlay(CANCEL_ROLE);
  981. }
  982. }
  983. get isCalendarPicker() {
  984. const { presentation } = this;
  985. return presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
  986. }
  987. connectedCallback() {
  988. this.clearFocusVisible = startFocusVisible(this.el).destroy;
  989. }
  990. disconnectedCallback() {
  991. if (this.clearFocusVisible) {
  992. this.clearFocusVisible();
  993. this.clearFocusVisible = undefined;
  994. }
  995. }
  996. initializeListeners() {
  997. this.initializeCalendarListener();
  998. this.initializeKeyboardListeners();
  999. }
  1000. componentDidLoad() {
  1001. const { el, intersectionTrackerRef } = this;
  1002. /**
  1003. * If a scrollable element is hidden using `display: none`,
  1004. * it will not have a scroll height meaning we cannot scroll elements
  1005. * into view. As a result, we will need to wait for the datetime to become
  1006. * visible if used inside of a modal or a popover otherwise the scrollable
  1007. * areas will not have the correct values snapped into place.
  1008. */
  1009. const visibleCallback = (entries) => {
  1010. const ev = entries[0];
  1011. if (!ev.isIntersecting) {
  1012. return;
  1013. }
  1014. this.initializeListeners();
  1015. /**
  1016. * TODO FW-2793: Datetime needs a frame to ensure that it
  1017. * can properly scroll contents into view. As a result
  1018. * we hide the scrollable content until after that frame
  1019. * so users do not see the content quickly shifting. The downside
  1020. * is that the content will pop into view a frame after. Maybe there
  1021. * is a better way to handle this?
  1022. */
  1023. writeTask(() => {
  1024. this.el.classList.add('datetime-ready');
  1025. });
  1026. };
  1027. const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01, root: el });
  1028. /**
  1029. * Use raf to avoid a race condition between the component loading and
  1030. * its display animation starting (such as when shown in a modal). This
  1031. * could cause the datetime to start at a visibility of 0, erroneously
  1032. * triggering the `hiddenIO` observer below.
  1033. */
  1034. raf(() => visibleIO === null || visibleIO === void 0 ? void 0 : visibleIO.observe(intersectionTrackerRef));
  1035. /**
  1036. * We need to clean up listeners when the datetime is hidden
  1037. * in a popover/modal so that we can properly scroll containers
  1038. * back into view if they are re-presented. When the datetime is hidden
  1039. * the scroll areas have scroll widths/heights of 0px, so any snapping
  1040. * we did originally has been lost.
  1041. */
  1042. const hiddenCallback = (entries) => {
  1043. const ev = entries[0];
  1044. if (ev.isIntersecting) {
  1045. return;
  1046. }
  1047. this.destroyInteractionListeners();
  1048. /**
  1049. * When datetime is hidden, we need to make sure that
  1050. * the month/year picker is closed. Otherwise,
  1051. * it will be open when the datetime re-appears
  1052. * and the scroll area of the calendar grid will be 0.
  1053. * As a result, the wrong month will be shown.
  1054. */
  1055. this.showMonthAndYear = false;
  1056. writeTask(() => {
  1057. this.el.classList.remove('datetime-ready');
  1058. });
  1059. };
  1060. const hiddenIO = new IntersectionObserver(hiddenCallback, { threshold: 0, root: el });
  1061. raf(() => hiddenIO === null || hiddenIO === void 0 ? void 0 : hiddenIO.observe(intersectionTrackerRef));
  1062. /**
  1063. * Datetime uses Ionic components that emit
  1064. * ionFocus and ionBlur. These events are
  1065. * composed meaning they will cross
  1066. * the shadow dom boundary. We need to
  1067. * stop propagation on these events otherwise
  1068. * developers will see 2 ionFocus or 2 ionBlur
  1069. * events at a time.
  1070. */
  1071. const root = getElementRoot(this.el);
  1072. root.addEventListener('ionFocus', (ev) => ev.stopPropagation());
  1073. root.addEventListener('ionBlur', (ev) => ev.stopPropagation());
  1074. }
  1075. /**
  1076. * When the presentation is changed, all calendar content is recreated,
  1077. * so we need to re-init behavior with the new elements.
  1078. */
  1079. componentDidRender() {
  1080. const { presentation, prevPresentation, calendarBodyRef, minParts, preferWheel, forceRenderDate } = this;
  1081. /**
  1082. * TODO(FW-2165)
  1083. * Remove this when https://bugs.webkit.org/show_bug.cgi?id=235960 is fixed.
  1084. * When using `min`, we add `scroll-snap-align: none`
  1085. * to the disabled month so that users cannot scroll to it.
  1086. * This triggers a bug in WebKit where the scroll position is reset.
  1087. * Since the month change logic is handled by a scroll listener,
  1088. * this causes the month to change leading to `scroll-snap-align`
  1089. * changing again, thus changing the scroll position again and causing
  1090. * an infinite loop.
  1091. * This issue only applies to the calendar grid, so we can disable
  1092. * it if the calendar grid is not being used.
  1093. */
  1094. const hasCalendarGrid = !preferWheel && ['date-time', 'time-date', 'date'].includes(presentation);
  1095. if (minParts !== undefined && hasCalendarGrid && calendarBodyRef) {
  1096. const workingMonth = calendarBodyRef.querySelector('.calendar-month:nth-of-type(1)');
  1097. /**
  1098. * We need to make sure the datetime is not in the process
  1099. * of scrolling to a new datetime value if the value
  1100. * is updated programmatically.
  1101. * Otherwise, the datetime will appear to not scroll at all because
  1102. * we are resetting the scroll position to the center of the view.
  1103. * Prior to the datetime's value being updated programmatically,
  1104. * the calendarBodyRef is scrolled such that the middle month is centered
  1105. * in the view. The below code updates the scroll position so the middle
  1106. * month is also centered in the view. Since the scroll position did not change,
  1107. * the scroll callback in this file does not fire,
  1108. * and the resolveForceDateScrolling promise never resolves.
  1109. */
  1110. if (workingMonth && forceRenderDate === undefined) {
  1111. calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
  1112. }
  1113. }
  1114. if (prevPresentation === null) {
  1115. this.prevPresentation = presentation;
  1116. return;
  1117. }
  1118. if (presentation === prevPresentation) {
  1119. return;
  1120. }
  1121. this.prevPresentation = presentation;
  1122. this.destroyInteractionListeners();
  1123. this.initializeListeners();
  1124. /**
  1125. * The month/year picker from the date interface
  1126. * should be closed as it is not available in non-date
  1127. * interfaces.
  1128. */
  1129. this.showMonthAndYear = false;
  1130. raf(() => {
  1131. this.ionRender.emit();
  1132. });
  1133. }
  1134. componentWillLoad() {
  1135. const { el, formatOptions, highlightedDates, multiple, presentation, preferWheel } = this;
  1136. if (multiple) {
  1137. if (presentation !== 'date') {
  1138. printIonWarning('[ion-datetime] - Multiple date selection is only supported for presentation="date".', el);
  1139. }
  1140. if (preferWheel) {
  1141. printIonWarning('[ion-datetime] - Multiple date selection is not supported with preferWheel="true".', el);
  1142. }
  1143. }
  1144. if (highlightedDates !== undefined) {
  1145. if (presentation !== 'date' && presentation !== 'date-time' && presentation !== 'time-date') {
  1146. printIonWarning('[ion-datetime] - The highlightedDates property is only supported with the date, date-time, and time-date presentations.', el);
  1147. }
  1148. if (preferWheel) {
  1149. printIonWarning('[ion-datetime] - The highlightedDates property is not supported with preferWheel="true".', el);
  1150. }
  1151. }
  1152. if (formatOptions) {
  1153. checkForPresentationFormatMismatch(el, presentation, formatOptions);
  1154. warnIfTimeZoneProvided(el, formatOptions);
  1155. }
  1156. const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues));
  1157. const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues));
  1158. const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues));
  1159. const yearValues = (this.parsedYearValues = convertToArrayOfNumbers(this.yearValues));
  1160. const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
  1161. const todayParts = (this.todayParts = parseDate(getToday()));
  1162. this.processMinParts();
  1163. this.processMaxParts();
  1164. this.defaultParts = getClosestValidDate({
  1165. refParts: todayParts,
  1166. monthValues,
  1167. dayValues,
  1168. yearValues,
  1169. hourValues,
  1170. minuteValues,
  1171. minParts: this.minParts,
  1172. maxParts: this.maxParts,
  1173. });
  1174. this.processValue(this.value);
  1175. this.emitStyle();
  1176. }
  1177. emitStyle() {
  1178. this.ionStyle.emit({
  1179. interactive: true,
  1180. datetime: true,
  1181. 'interactive-disabled': this.disabled,
  1182. });
  1183. }
  1184. /**
  1185. * Universal render methods
  1186. * These are pieces of datetime that
  1187. * are rendered independently of presentation.
  1188. */
  1189. renderFooter() {
  1190. const { disabled, readonly, showDefaultButtons, showClearButton } = this;
  1191. /**
  1192. * The cancel, clear, and confirm buttons
  1193. * should not be interactive if the datetime
  1194. * is disabled or readonly.
  1195. */
  1196. const isButtonDisabled = disabled || readonly;
  1197. const hasSlottedButtons = this.el.querySelector('[slot="buttons"]') !== null;
  1198. if (!hasSlottedButtons && !showDefaultButtons && !showClearButton) {
  1199. return;
  1200. }
  1201. const clearButtonClick = () => {
  1202. this.reset();
  1203. this.setValue(undefined);
  1204. };
  1205. /**
  1206. * By default we render two buttons:
  1207. * Cancel - Dismisses the datetime and
  1208. * does not update the `value` prop.
  1209. * OK - Dismisses the datetime and
  1210. * updates the `value` prop.
  1211. */
  1212. return (h("div", { class: "datetime-footer" }, h("div", { class: "datetime-buttons" }, h("div", { class: {
  1213. ['datetime-action-buttons']: true,
  1214. ['has-clear-button']: this.showClearButton,
  1215. } }, h("slot", { name: "buttons" }, h("ion-buttons", null, showDefaultButtons && (h("ion-button", { id: "cancel-button", color: this.color, onClick: () => this.cancel(true), disabled: isButtonDisabled }, this.cancelText)), h("div", { class: "datetime-action-buttons-container" }, showClearButton && (h("ion-button", { id: "clear-button", color: this.color, onClick: () => clearButtonClick(), disabled: isButtonDisabled }, this.clearText)), showDefaultButtons && (h("ion-button", { id: "confirm-button", color: this.color, onClick: () => this.confirm(true), disabled: isButtonDisabled }, this.doneText)))))))));
  1216. }
  1217. /**
  1218. * Wheel picker render methods
  1219. */
  1220. renderWheelPicker(forcePresentation = this.presentation) {
  1221. /**
  1222. * If presentation="time-date" we switch the
  1223. * order of the render array here instead of
  1224. * manually reordering each date/time picker
  1225. * column with CSS. This allows for additional
  1226. * flexibility if we need to render subsets
  1227. * of the date/time data or do additional ordering
  1228. * within the child render functions.
  1229. */
  1230. const renderArray = forcePresentation === 'time-date'
  1231. ? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)]
  1232. : [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)];
  1233. return h("ion-picker", null, renderArray);
  1234. }
  1235. renderDatePickerColumns(forcePresentation) {
  1236. return forcePresentation === 'date-time' || forcePresentation === 'time-date'
  1237. ? this.renderCombinedDatePickerColumn()
  1238. : this.renderIndividualDatePickerColumns(forcePresentation);
  1239. }
  1240. renderCombinedDatePickerColumn() {
  1241. const { defaultParts, disabled, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
  1242. const activePart = this.getActivePartsWithFallback();
  1243. /**
  1244. * By default, generate a range of 3 months:
  1245. * Previous month, current month, and next month
  1246. */
  1247. const monthsToRender = generateMonths(workingParts);
  1248. const lastMonth = monthsToRender[monthsToRender.length - 1];
  1249. /**
  1250. * Ensure that users can select the entire window of dates.
  1251. */
  1252. monthsToRender[0].day = 1;
  1253. lastMonth.day = getNumDaysInMonth(lastMonth.month, lastMonth.year);
  1254. /**
  1255. * Narrow the dates rendered based on min/max dates (if any).
  1256. * The `min` date is used if the min is after the generated min month.
  1257. * The `max` date is used if the max is before the generated max month.
  1258. * This ensures that the sliding window always stays at 3 months
  1259. * but still allows future dates to be lazily rendered based on any min/max
  1260. * constraints.
  1261. */
  1262. const min = minParts !== undefined && isAfter(minParts, monthsToRender[0]) ? minParts : monthsToRender[0];
  1263. const max = maxParts !== undefined && isBefore(maxParts, lastMonth) ? maxParts : lastMonth;
  1264. const result = getCombinedDateColumnData(locale, todayParts, min, max, this.parsedDayValues, this.parsedMonthValues);
  1265. let items = result.items;
  1266. const parts = result.parts;
  1267. if (isDateEnabled) {
  1268. items = items.map((itemObject, index) => {
  1269. const referenceParts = parts[index];
  1270. let disabled;
  1271. try {
  1272. /**
  1273. * The `isDateEnabled` implementation is try-catch wrapped
  1274. * to prevent exceptions in the user's function from
  1275. * interrupting the calendar rendering.
  1276. */
  1277. disabled = !isDateEnabled(convertDataToISO(referenceParts));
  1278. }
  1279. catch (e) {
  1280. printIonError('[ion-datetime] - Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', e);
  1281. }
  1282. return Object.assign(Object.assign({}, itemObject), { disabled });
  1283. });
  1284. }
  1285. /**
  1286. * If we have selected a day already, then default the column
  1287. * to that value. Otherwise, set it to the default date.
  1288. */
  1289. const todayString = workingParts.day !== null
  1290. ? `${workingParts.year}-${workingParts.month}-${workingParts.day}`
  1291. : `${defaultParts.year}-${defaultParts.month}-${defaultParts.day}`;
  1292. return (h("ion-picker-column", { "aria-label": "Select a date", class: "date-column", color: this.color, disabled: disabled, value: todayString, onIonChange: (ev) => {
  1293. const { value } = ev.detail;
  1294. const findPart = parts.find(({ month, day, year }) => value === `${year}-${month}-${day}`);
  1295. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), findPart));
  1296. this.setActiveParts(Object.assign(Object.assign({}, activePart), findPart));
  1297. ev.stopPropagation();
  1298. } }, items.map((item) => (h("ion-picker-column-option", { part: item.value === todayString ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: item.value, disabled: item.disabled, value: item.value }, item.text)))));
  1299. }
  1300. renderIndividualDatePickerColumns(forcePresentation) {
  1301. const { workingParts, isDateEnabled } = this;
  1302. const shouldRenderMonths = forcePresentation !== 'year' && forcePresentation !== 'time';
  1303. const months = shouldRenderMonths
  1304. ? getMonthColumnData(this.locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues)
  1305. : [];
  1306. const shouldRenderDays = forcePresentation === 'date';
  1307. let days = shouldRenderDays
  1308. ? getDayColumnData(this.locale, workingParts, this.minParts, this.maxParts, this.parsedDayValues)
  1309. : [];
  1310. if (isDateEnabled) {
  1311. days = days.map((dayObject) => {
  1312. const { value } = dayObject;
  1313. const valueNum = typeof value === 'string' ? parseInt(value) : value;
  1314. const referenceParts = {
  1315. month: workingParts.month,
  1316. day: valueNum,
  1317. year: workingParts.year,
  1318. };
  1319. let disabled;
  1320. try {
  1321. /**
  1322. * The `isDateEnabled` implementation is try-catch wrapped
  1323. * to prevent exceptions in the user's function from
  1324. * interrupting the calendar rendering.
  1325. */
  1326. disabled = !isDateEnabled(convertDataToISO(referenceParts));
  1327. }
  1328. catch (e) {
  1329. printIonError('[ion-datetime] - Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', e);
  1330. }
  1331. return Object.assign(Object.assign({}, dayObject), { disabled });
  1332. });
  1333. }
  1334. const shouldRenderYears = forcePresentation !== 'month' && forcePresentation !== 'time';
  1335. const years = shouldRenderYears
  1336. ? getYearColumnData(this.locale, this.defaultParts, this.minParts, this.maxParts, this.parsedYearValues)
  1337. : [];
  1338. /**
  1339. * Certain locales show the day before the month.
  1340. */
  1341. const showMonthFirst = isMonthFirstLocale(this.locale, { month: 'numeric', day: 'numeric' });
  1342. let renderArray = [];
  1343. if (showMonthFirst) {
  1344. renderArray = [
  1345. this.renderMonthPickerColumn(months),
  1346. this.renderDayPickerColumn(days),
  1347. this.renderYearPickerColumn(years),
  1348. ];
  1349. }
  1350. else {
  1351. renderArray = [
  1352. this.renderDayPickerColumn(days),
  1353. this.renderMonthPickerColumn(months),
  1354. this.renderYearPickerColumn(years),
  1355. ];
  1356. }
  1357. return renderArray;
  1358. }
  1359. renderDayPickerColumn(days) {
  1360. var _a;
  1361. if (days.length === 0) {
  1362. return [];
  1363. }
  1364. const { disabled, workingParts } = this;
  1365. const activePart = this.getActivePartsWithFallback();
  1366. const pickerColumnValue = (_a = (workingParts.day !== null ? workingParts.day : this.defaultParts.day)) !== null && _a !== void 0 ? _a : undefined;
  1367. return (h("ion-picker-column", { "aria-label": "Select a day", class: "day-column", color: this.color, disabled: disabled, value: pickerColumnValue, onIonChange: (ev) => {
  1368. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { day: ev.detail.value }));
  1369. this.setActiveParts(Object.assign(Object.assign({}, activePart), { day: ev.detail.value }));
  1370. ev.stopPropagation();
  1371. } }, days.map((day) => (h("ion-picker-column-option", { part: day.value === pickerColumnValue ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: day.value, disabled: day.disabled, value: day.value }, day.text)))));
  1372. }
  1373. renderMonthPickerColumn(months) {
  1374. if (months.length === 0) {
  1375. return [];
  1376. }
  1377. const { disabled, workingParts } = this;
  1378. const activePart = this.getActivePartsWithFallback();
  1379. return (h("ion-picker-column", { "aria-label": "Select a month", class: "month-column", color: this.color, disabled: disabled, value: workingParts.month, onIonChange: (ev) => {
  1380. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { month: ev.detail.value }));
  1381. this.setActiveParts(Object.assign(Object.assign({}, activePart), { month: ev.detail.value }));
  1382. ev.stopPropagation();
  1383. } }, months.map((month) => (h("ion-picker-column-option", { part: month.value === workingParts.month ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: month.value, disabled: month.disabled, value: month.value }, month.text)))));
  1384. }
  1385. renderYearPickerColumn(years) {
  1386. if (years.length === 0) {
  1387. return [];
  1388. }
  1389. const { disabled, workingParts } = this;
  1390. const activePart = this.getActivePartsWithFallback();
  1391. return (h("ion-picker-column", { "aria-label": "Select a year", class: "year-column", color: this.color, disabled: disabled, value: workingParts.year, onIonChange: (ev) => {
  1392. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { year: ev.detail.value }));
  1393. this.setActiveParts(Object.assign(Object.assign({}, activePart), { year: ev.detail.value }));
  1394. ev.stopPropagation();
  1395. } }, years.map((year) => (h("ion-picker-column-option", { part: year.value === workingParts.year ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: year.value, disabled: year.disabled, value: year.value }, year.text)))));
  1396. }
  1397. renderTimePickerColumns(forcePresentation) {
  1398. if (['date', 'month', 'month-year', 'year'].includes(forcePresentation)) {
  1399. return [];
  1400. }
  1401. /**
  1402. * If a user has not selected a date,
  1403. * then we should show all times. If the
  1404. * user has selected a date (even if it has
  1405. * not been confirmed yet), we should apply
  1406. * the max and min restrictions so that the
  1407. * time picker shows values that are
  1408. * appropriate for the selected date.
  1409. */
  1410. const activePart = this.getActivePart();
  1411. const userHasSelectedDate = activePart !== undefined;
  1412. const { hoursData, minutesData, dayPeriodData } = getTimeColumnsData(this.locale, this.workingParts, this.hourCycle, userHasSelectedDate ? this.minParts : undefined, userHasSelectedDate ? this.maxParts : undefined, this.parsedHourValues, this.parsedMinuteValues);
  1413. return [
  1414. this.renderHourPickerColumn(hoursData),
  1415. this.renderMinutePickerColumn(minutesData),
  1416. this.renderDayPeriodPickerColumn(dayPeriodData),
  1417. ];
  1418. }
  1419. renderHourPickerColumn(hoursData) {
  1420. const { disabled, workingParts } = this;
  1421. if (hoursData.length === 0)
  1422. return [];
  1423. const activePart = this.getActivePartsWithFallback();
  1424. return (h("ion-picker-column", { "aria-label": "Select an hour", color: this.color, disabled: disabled, value: activePart.hour, numericInput: true, onIonChange: (ev) => {
  1425. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { hour: ev.detail.value }));
  1426. this.setActiveParts(Object.assign(Object.assign({}, this.getActivePartsWithFallback()), { hour: ev.detail.value }));
  1427. ev.stopPropagation();
  1428. } }, hoursData.map((hour) => (h("ion-picker-column-option", { part: hour.value === activePart.hour ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: hour.value, disabled: hour.disabled, value: hour.value }, hour.text)))));
  1429. }
  1430. renderMinutePickerColumn(minutesData) {
  1431. const { disabled, workingParts } = this;
  1432. if (minutesData.length === 0)
  1433. return [];
  1434. const activePart = this.getActivePartsWithFallback();
  1435. return (h("ion-picker-column", { "aria-label": "Select a minute", color: this.color, disabled: disabled, value: activePart.minute, numericInput: true, onIonChange: (ev) => {
  1436. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { minute: ev.detail.value }));
  1437. this.setActiveParts(Object.assign(Object.assign({}, this.getActivePartsWithFallback()), { minute: ev.detail.value }));
  1438. ev.stopPropagation();
  1439. } }, minutesData.map((minute) => (h("ion-picker-column-option", { part: minute.value === activePart.minute ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: minute.value, disabled: minute.disabled, value: minute.value }, minute.text)))));
  1440. }
  1441. renderDayPeriodPickerColumn(dayPeriodData) {
  1442. const { disabled, workingParts } = this;
  1443. if (dayPeriodData.length === 0) {
  1444. return [];
  1445. }
  1446. const activePart = this.getActivePartsWithFallback();
  1447. const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale);
  1448. return (h("ion-picker-column", { "aria-label": "Select a day period", style: isDayPeriodRTL ? { order: '-1' } : {}, color: this.color, disabled: disabled, value: activePart.ampm, onIonChange: (ev) => {
  1449. const hour = calculateHourFromAMPM(workingParts, ev.detail.value);
  1450. this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { ampm: ev.detail.value, hour }));
  1451. this.setActiveParts(Object.assign(Object.assign({}, this.getActivePartsWithFallback()), { ampm: ev.detail.value, hour }));
  1452. ev.stopPropagation();
  1453. } }, dayPeriodData.map((dayPeriod) => (h("ion-picker-column-option", { part: dayPeriod.value === activePart.ampm ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: dayPeriod.value, disabled: dayPeriod.disabled, value: dayPeriod.value }, dayPeriod.text)))));
  1454. }
  1455. renderWheelView(forcePresentation) {
  1456. const { locale } = this;
  1457. const showMonthFirst = isMonthFirstLocale(locale);
  1458. const columnOrder = showMonthFirst ? 'month-first' : 'year-first';
  1459. return (h("div", { class: {
  1460. [`wheel-order-${columnOrder}`]: true,
  1461. } }, this.renderWheelPicker(forcePresentation)));
  1462. }
  1463. /**
  1464. * Grid Render Methods
  1465. */
  1466. renderCalendarHeader(mode) {
  1467. const { disabled } = this;
  1468. const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp;
  1469. const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp;
  1470. const prevMonthDisabled = disabled || isPrevMonthDisabled(this.workingParts, this.minParts, this.maxParts);
  1471. const nextMonthDisabled = disabled || isNextMonthDisabled(this.workingParts, this.maxParts);
  1472. // don't use the inheritAttributes util because it removes dir from the host, and we still need that
  1473. const hostDir = this.el.getAttribute('dir') || undefined;
  1474. return (h("div", { class: "calendar-header" }, h("div", { class: "calendar-action-buttons" }, h("div", { class: "calendar-month-year" }, h("button", { class: {
  1475. 'calendar-month-year-toggle': true,
  1476. 'ion-activatable': true,
  1477. 'ion-focusable': true,
  1478. }, part: "month-year-button", disabled: disabled, "aria-label": this.showMonthAndYear ? 'Hide year picker' : 'Show year picker', onClick: () => this.toggleMonthAndYearView() }, h("span", { id: "toggle-wrapper" }, getMonthAndYear(this.locale, this.workingParts), h("ion-icon", { "aria-hidden": "true", icon: this.showMonthAndYear ? expandedIcon : collapsedIcon, lazy: false, flipRtl: true })), mode === 'md' && h("ion-ripple-effect", null))), h("div", { class: "calendar-next-prev" }, h("ion-buttons", null, h("ion-button", { "aria-label": "Previous month", disabled: prevMonthDisabled, onClick: () => this.prevMonth() }, h("ion-icon", { dir: hostDir, "aria-hidden": "true", slot: "icon-only", icon: chevronBack, lazy: false, flipRtl: true })), h("ion-button", { "aria-label": "Next month", disabled: nextMonthDisabled, onClick: () => this.nextMonth() }, h("ion-icon", { dir: hostDir, "aria-hidden": "true", slot: "icon-only", icon: chevronForward, lazy: false, flipRtl: true }))))), h("div", { class: "calendar-days-of-week", "aria-hidden": "true" }, getDaysOfWeek(this.locale, mode, this.firstDayOfWeek % 7).map((d) => {
  1479. return h("div", { class: "day-of-week" }, d);
  1480. }))));
  1481. }
  1482. renderMonth(month, year) {
  1483. const { disabled, readonly } = this;
  1484. const yearAllowed = this.parsedYearValues === undefined || this.parsedYearValues.includes(year);
  1485. const monthAllowed = this.parsedMonthValues === undefined || this.parsedMonthValues.includes(month);
  1486. const isCalMonthDisabled = !yearAllowed || !monthAllowed;
  1487. const isDatetimeDisabled = disabled || readonly;
  1488. const swipeDisabled = disabled ||
  1489. isMonthDisabled({
  1490. month,
  1491. year,
  1492. day: null,
  1493. }, {
  1494. // The day is not used when checking if a month is disabled.
  1495. // Users should be able to access the min or max month, even if the
  1496. // min/max date is out of bounds (e.g. min is set to Feb 15, Feb should not be disabled).
  1497. minParts: Object.assign(Object.assign({}, this.minParts), { day: null }),
  1498. maxParts: Object.assign(Object.assign({}, this.maxParts), { day: null }),
  1499. });
  1500. // The working month should never have swipe disabled.
  1501. // Otherwise the CSS scroll snap will not work and the user
  1502. // can free-scroll the calendar.
  1503. const isWorkingMonth = this.workingParts.month === month && this.workingParts.year === year;
  1504. const activePart = this.getActivePartsWithFallback();
  1505. return (h("div", { "aria-hidden": !isWorkingMonth ? 'true' : null, class: {
  1506. 'calendar-month': true,
  1507. // Prevents scroll snap swipe gestures for months outside of the min/max bounds
  1508. 'calendar-month-disabled': !isWorkingMonth && swipeDisabled,
  1509. } }, h("div", { class: "calendar-month-grid" }, getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
  1510. const { day, dayOfWeek } = dateObject;
  1511. const { el, highlightedDates, isDateEnabled, multiple } = this;
  1512. const referenceParts = { month, day, year };
  1513. const isCalendarPadding = day === null;
  1514. const { isActive, isToday, ariaLabel, ariaSelected, disabled: isDayDisabled, text, } = getCalendarDayState(this.locale, referenceParts, this.activeParts, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues);
  1515. const dateIsoString = convertDataToISO(referenceParts);
  1516. let isCalDayDisabled = isCalMonthDisabled || isDayDisabled;
  1517. if (!isCalDayDisabled && isDateEnabled !== undefined) {
  1518. try {
  1519. /**
  1520. * The `isDateEnabled` implementation is try-catch wrapped
  1521. * to prevent exceptions in the user's function from
  1522. * interrupting the calendar rendering.
  1523. */
  1524. isCalDayDisabled = !isDateEnabled(dateIsoString);
  1525. }
  1526. catch (e) {
  1527. printIonError('[ion-datetime] - Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', el, e);
  1528. }
  1529. }
  1530. /**
  1531. * Some days are constrained through max & min or allowed dates
  1532. * and also disabled because the component is readonly or disabled.
  1533. * These need to be displayed differently.
  1534. */
  1535. const isCalDayConstrained = isCalDayDisabled && isDatetimeDisabled;
  1536. const isButtonDisabled = isCalDayDisabled || isDatetimeDisabled;
  1537. let dateStyle = undefined;
  1538. /**
  1539. * Custom highlight styles should not override the style for selected dates,
  1540. * nor apply to "filler days" at the start of the grid.
  1541. */
  1542. if (highlightedDates !== undefined && !isActive && day !== null) {
  1543. dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
  1544. }
  1545. let dateParts = undefined;
  1546. // "Filler days" at the beginning of the grid should not get the calendar day
  1547. // CSS parts added to them
  1548. if (!isCalendarPadding) {
  1549. dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${isCalDayDisabled ? ' disabled' : ''}`;
  1550. }
  1551. return (h("div", { class: "calendar-day-wrapper" }, h("button", {
  1552. // We need to use !important for the inline styles here because
  1553. // otherwise the CSS shadow parts will override these styles.
  1554. // See https://github.com/WICG/webcomponents/issues/847
  1555. // Both the CSS shadow parts and highlightedDates styles are
  1556. // provided by the developer, but highlightedDates styles should
  1557. // always take priority.
  1558. ref: (el) => {
  1559. if (el) {
  1560. el.style.setProperty('color', `${dateStyle ? dateStyle.textColor : ''}`, 'important');
  1561. el.style.setProperty('background-color', `${dateStyle ? dateStyle.backgroundColor : ''}`, 'important');
  1562. }
  1563. }, tabindex: "-1", "data-day": day, "data-month": month, "data-year": year, "data-index": index, "data-day-of-week": dayOfWeek, disabled: isButtonDisabled, class: {
  1564. 'calendar-day-padding': isCalendarPadding,
  1565. 'calendar-day': true,
  1566. 'calendar-day-active': isActive,
  1567. 'calendar-day-constrained': isCalDayConstrained,
  1568. 'calendar-day-today': isToday,
  1569. }, part: dateParts, "aria-hidden": isCalendarPadding ? 'true' : null, "aria-selected": ariaSelected, "aria-label": ariaLabel, onClick: () => {
  1570. if (isCalendarPadding) {
  1571. return;
  1572. }
  1573. this.setWorkingParts(Object.assign(Object.assign({}, this.workingParts), { month,
  1574. day,
  1575. year }));
  1576. // multiple only needs date info, so we can wipe out other fields like time
  1577. if (multiple) {
  1578. this.setActiveParts({
  1579. month,
  1580. day,
  1581. year,
  1582. }, isActive);
  1583. }
  1584. else {
  1585. this.setActiveParts(Object.assign(Object.assign({}, activePart), { month,
  1586. day,
  1587. year }));
  1588. }
  1589. }
  1590. }, text)));
  1591. }))));
  1592. }
  1593. renderCalendarBody() {
  1594. return (h("div", { class: "calendar-body ion-focusable", ref: (el) => (this.calendarBodyRef = el), tabindex: "0" }, generateMonths(this.workingParts, this.forceRenderDate).map(({ month, year }) => {
  1595. return this.renderMonth(month, year);
  1596. })));
  1597. }
  1598. renderCalendar(mode) {
  1599. return (h("div", { class: "datetime-calendar", key: "datetime-calendar" }, this.renderCalendarHeader(mode), this.renderCalendarBody()));
  1600. }
  1601. renderTimeLabel() {
  1602. const hasSlottedTimeLabel = this.el.querySelector('[slot="time-label"]') !== null;
  1603. if (!hasSlottedTimeLabel && !this.showDefaultTimeLabel) {
  1604. return;
  1605. }
  1606. return h("slot", { name: "time-label" }, "Time");
  1607. }
  1608. renderTimeOverlay() {
  1609. const { disabled, hourCycle, isTimePopoverOpen, locale, formatOptions } = this;
  1610. const computedHourCycle = getHourCycle(locale, hourCycle);
  1611. const activePart = this.getActivePartsWithFallback();
  1612. return [
  1613. h("div", { class: "time-header" }, this.renderTimeLabel()),
  1614. h("button", { class: {
  1615. 'time-body': true,
  1616. 'time-body-active': isTimePopoverOpen,
  1617. }, part: `time-button${isTimePopoverOpen ? ' active' : ''}`, "aria-expanded": "false", "aria-haspopup": "true", disabled: disabled, onClick: async (ev) => {
  1618. const { popoverRef } = this;
  1619. if (popoverRef) {
  1620. this.isTimePopoverOpen = true;
  1621. popoverRef.present(new CustomEvent('ionShadowTarget', {
  1622. detail: {
  1623. ionShadowTarget: ev.target,
  1624. },
  1625. }));
  1626. await popoverRef.onWillDismiss();
  1627. this.isTimePopoverOpen = false;
  1628. }
  1629. } }, getLocalizedTime(locale, activePart, computedHourCycle, formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time)),
  1630. h("ion-popover", { alignment: "center", translucent: true, overlayIndex: 1, arrow: false, onWillPresent: (ev) => {
  1631. /**
  1632. * Intersection Observers do not consistently fire between Blink and Webkit
  1633. * when toggling the visibility of the popover and trying to scroll the picker
  1634. * column to the correct time value.
  1635. *
  1636. * This will correctly scroll the element position to the correct time value,
  1637. * before the popover is fully presented.
  1638. */
  1639. const cols = ev.target.querySelectorAll('ion-picker-column');
  1640. // TODO (FW-615): Potentially remove this when intersection observers are fixed in picker column
  1641. cols.forEach((col) => col.scrollActiveItemIntoView());
  1642. }, style: {
  1643. '--offset-y': '-10px',
  1644. '--min-width': 'fit-content',
  1645. },
  1646. // Allow native browser keyboard events to support up/down/home/end key
  1647. // navigation within the time picker.
  1648. keyboardEvents: true, ref: (el) => (this.popoverRef = el) }, this.renderWheelPicker('time')),
  1649. ];
  1650. }
  1651. getHeaderSelectedDateText() {
  1652. var _a;
  1653. const { activeParts, formatOptions, multiple, titleSelectedDatesFormatter } = this;
  1654. const isArray = Array.isArray(activeParts);
  1655. let headerText;
  1656. if (multiple && isArray && activeParts.length !== 1) {
  1657. headerText = `${activeParts.length} days`; // default/fallback for multiple selection
  1658. if (titleSelectedDatesFormatter !== undefined) {
  1659. try {
  1660. headerText = titleSelectedDatesFormatter(convertDataToISO(activeParts));
  1661. }
  1662. catch (e) {
  1663. printIonError('[ion-datetime] - Exception in provided `titleSelectedDatesFormatter`:', e);
  1664. }
  1665. }
  1666. }
  1667. else {
  1668. // for exactly 1 day selected (multiple set or not), show a formatted version of that
  1669. headerText = getLocalizedDateTime(this.locale, this.getActivePartsWithFallback(), (_a = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _a !== void 0 ? _a : { weekday: 'short', month: 'short', day: 'numeric' });
  1670. }
  1671. return headerText;
  1672. }
  1673. renderHeader(showExpandedHeader = true) {
  1674. const hasSlottedTitle = this.el.querySelector('[slot="title"]') !== null;
  1675. if (!hasSlottedTitle && !this.showDefaultTitle) {
  1676. return;
  1677. }
  1678. return (h("div", { class: "datetime-header" }, h("div", { class: "datetime-title" }, h("slot", { name: "title" }, "Select Date")), showExpandedHeader && h("div", { class: "datetime-selected-date" }, this.getHeaderSelectedDateText())));
  1679. }
  1680. /**
  1681. * Render time picker inside of datetime.
  1682. * Do not pass color prop to segment on
  1683. * iOS mode. MD segment has been customized and
  1684. * should take on the color prop, but iOS
  1685. * should just be the default segment.
  1686. */
  1687. renderTime() {
  1688. const { presentation } = this;
  1689. const timeOnlyPresentation = presentation === 'time';
  1690. return (h("div", { class: "datetime-time" }, timeOnlyPresentation ? this.renderWheelPicker() : this.renderTimeOverlay()));
  1691. }
  1692. /**
  1693. * Renders the month/year picker that is
  1694. * displayed on the calendar grid.
  1695. * The .datetime-year class has additional
  1696. * styles that let us show/hide the
  1697. * picker when the user clicks on the
  1698. * toggle in the calendar header.
  1699. */
  1700. renderCalendarViewMonthYearPicker() {
  1701. return h("div", { class: "datetime-year" }, this.renderWheelView('month-year'));
  1702. }
  1703. /**
  1704. * Render entry point
  1705. * All presentation types are rendered from here.
  1706. */
  1707. renderDatetime(mode) {
  1708. const { presentation, preferWheel } = this;
  1709. /**
  1710. * Certain presentation types have separate grid and wheel displays.
  1711. * If preferWheel is true then we should show a wheel picker instead.
  1712. */
  1713. const hasWheelVariant = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
  1714. if (preferWheel && hasWheelVariant) {
  1715. return [this.renderHeader(false), this.renderWheelView(), this.renderFooter()];
  1716. }
  1717. switch (presentation) {
  1718. case 'date-time':
  1719. return [
  1720. this.renderHeader(),
  1721. this.renderCalendar(mode),
  1722. this.renderCalendarViewMonthYearPicker(),
  1723. this.renderTime(),
  1724. this.renderFooter(),
  1725. ];
  1726. case 'time-date':
  1727. return [
  1728. this.renderHeader(),
  1729. this.renderTime(),
  1730. this.renderCalendar(mode),
  1731. this.renderCalendarViewMonthYearPicker(),
  1732. this.renderFooter(),
  1733. ];
  1734. case 'time':
  1735. return [this.renderHeader(false), this.renderTime(), this.renderFooter()];
  1736. case 'month':
  1737. case 'month-year':
  1738. case 'year':
  1739. return [this.renderHeader(false), this.renderWheelView(), this.renderFooter()];
  1740. default:
  1741. return [
  1742. this.renderHeader(),
  1743. this.renderCalendar(mode),
  1744. this.renderCalendarViewMonthYearPicker(),
  1745. this.renderFooter(),
  1746. ];
  1747. }
  1748. }
  1749. render() {
  1750. const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size, isGridStyle, } = this;
  1751. const mode = getIonMode(this);
  1752. const isMonthAndYearPresentation = presentation === 'year' || presentation === 'month' || presentation === 'month-year';
  1753. const shouldShowMonthAndYear = showMonthAndYear || isMonthAndYearPresentation;
  1754. const monthYearPickerOpen = showMonthAndYear && !isMonthAndYearPresentation;
  1755. const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
  1756. const hasWheelVariant = hasDatePresentation && preferWheel;
  1757. renderHiddenInput(true, el, name, formatValue(value), disabled);
  1758. return (h(Host, { key: 'c3dfea8f46fcbcef38eb9e8a69b1b46a4e4b82fd', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
  1759. [mode]: true,
  1760. ['datetime-readonly']: readonly,
  1761. ['datetime-disabled']: disabled,
  1762. 'show-month-and-year': shouldShowMonthAndYear,
  1763. 'month-year-picker-open': monthYearPickerOpen,
  1764. [`datetime-presentation-${presentation}`]: true,
  1765. [`datetime-size-${size}`]: true,
  1766. [`datetime-prefer-wheel`]: hasWheelVariant,
  1767. [`datetime-grid`]: isGridStyle,
  1768. })) }, h("div", { key: '75c91243cf6a51f44b83d7cf7d8c0c96bfd3c83f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
  1769. }
  1770. get el() { return this; }
  1771. static get watchers() { return {
  1772. "formatOptions": ["formatOptionsChanged"],
  1773. "disabled": ["disabledChanged"],
  1774. "min": ["minChanged"],
  1775. "max": ["maxChanged"],
  1776. "presentation": ["presentationChanged"],
  1777. "yearValues": ["yearValuesChanged"],
  1778. "monthValues": ["monthValuesChanged"],
  1779. "dayValues": ["dayValuesChanged"],
  1780. "hourValues": ["hourValuesChanged"],
  1781. "minuteValues": ["minuteValuesChanged"],
  1782. "value": ["valueChanged"]
  1783. }; }
  1784. static get style() { return {
  1785. ios: IonDatetimeIosStyle0,
  1786. md: IonDatetimeMdStyle0
  1787. }; }
  1788. }, [33, "ion-datetime", {
  1789. "color": [1],
  1790. "name": [1],
  1791. "disabled": [4],
  1792. "formatOptions": [16],
  1793. "readonly": [4],
  1794. "isDateEnabled": [16],
  1795. "min": [1025],
  1796. "max": [1025],
  1797. "presentation": [1],
  1798. "cancelText": [1, "cancel-text"],
  1799. "doneText": [1, "done-text"],
  1800. "clearText": [1, "clear-text"],
  1801. "yearValues": [8, "year-values"],
  1802. "monthValues": [8, "month-values"],
  1803. "dayValues": [8, "day-values"],
  1804. "hourValues": [8, "hour-values"],
  1805. "minuteValues": [8, "minute-values"],
  1806. "locale": [1],
  1807. "firstDayOfWeek": [2, "first-day-of-week"],
  1808. "titleSelectedDatesFormatter": [16],
  1809. "multiple": [4],
  1810. "highlightedDates": [16],
  1811. "value": [1025],
  1812. "showDefaultTitle": [4, "show-default-title"],
  1813. "showDefaultButtons": [4, "show-default-buttons"],
  1814. "showClearButton": [4, "show-clear-button"],
  1815. "showDefaultTimeLabel": [4, "show-default-time-label"],
  1816. "hourCycle": [1, "hour-cycle"],
  1817. "size": [1],
  1818. "preferWheel": [4, "prefer-wheel"],
  1819. "showMonthAndYear": [32],
  1820. "activeParts": [32],
  1821. "workingParts": [32],
  1822. "isTimePopoverOpen": [32],
  1823. "forceRenderDate": [32],
  1824. "confirm": [64],
  1825. "reset": [64],
  1826. "cancel": [64]
  1827. }, undefined, {
  1828. "formatOptions": ["formatOptionsChanged"],
  1829. "disabled": ["disabledChanged"],
  1830. "min": ["minChanged"],
  1831. "max": ["maxChanged"],
  1832. "presentation": ["presentationChanged"],
  1833. "yearValues": ["yearValuesChanged"],
  1834. "monthValues": ["monthValuesChanged"],
  1835. "dayValues": ["dayValuesChanged"],
  1836. "hourValues": ["hourValuesChanged"],
  1837. "minuteValues": ["minuteValuesChanged"],
  1838. "value": ["valueChanged"]
  1839. }]);
  1840. let datetimeIds = 0;
  1841. const CANCEL_ROLE = 'datetime-cancel';
  1842. const CONFIRM_ROLE = 'datetime-confirm';
  1843. const WHEEL_ITEM_PART = 'wheel-item';
  1844. const WHEEL_ITEM_ACTIVE_PART = `active`;
  1845. function defineCustomElement$1() {
  1846. if (typeof customElements === "undefined") {
  1847. return;
  1848. }
  1849. const components = ["ion-datetime", "ion-backdrop", "ion-button", "ion-buttons", "ion-icon", "ion-picker", "ion-picker-column", "ion-picker-column-option", "ion-popover", "ion-ripple-effect"];
  1850. components.forEach(tagName => { switch (tagName) {
  1851. case "ion-datetime":
  1852. if (!customElements.get(tagName)) {
  1853. customElements.define(tagName, Datetime);
  1854. }
  1855. break;
  1856. case "ion-backdrop":
  1857. if (!customElements.get(tagName)) {
  1858. defineCustomElement$a();
  1859. }
  1860. break;
  1861. case "ion-button":
  1862. if (!customElements.get(tagName)) {
  1863. defineCustomElement$9();
  1864. }
  1865. break;
  1866. case "ion-buttons":
  1867. if (!customElements.get(tagName)) {
  1868. defineCustomElement$8();
  1869. }
  1870. break;
  1871. case "ion-icon":
  1872. if (!customElements.get(tagName)) {
  1873. defineCustomElement$7();
  1874. }
  1875. break;
  1876. case "ion-picker":
  1877. if (!customElements.get(tagName)) {
  1878. defineCustomElement$6();
  1879. }
  1880. break;
  1881. case "ion-picker-column":
  1882. if (!customElements.get(tagName)) {
  1883. defineCustomElement$5();
  1884. }
  1885. break;
  1886. case "ion-picker-column-option":
  1887. if (!customElements.get(tagName)) {
  1888. defineCustomElement$4();
  1889. }
  1890. break;
  1891. case "ion-popover":
  1892. if (!customElements.get(tagName)) {
  1893. defineCustomElement$3();
  1894. }
  1895. break;
  1896. case "ion-ripple-effect":
  1897. if (!customElements.get(tagName)) {
  1898. defineCustomElement$2();
  1899. }
  1900. break;
  1901. } });
  1902. }
  1903. const IonDatetime = Datetime;
  1904. const defineCustomElement = defineCustomElement$1;
  1905. export { IonDatetime, defineCustomElement };