@ionic/core 7.3.5-dev.11694621267.1e5f63c2 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/components/button-active.js +1 -1
  2. package/components/checkbox.js +6 -3
  3. package/components/data.js +31 -7
  4. package/components/index.js +2 -2
  5. package/components/index2.js +203 -429
  6. package/components/index3.js +429 -203
  7. package/components/ion-datetime-button.js +3 -0
  8. package/components/ion-datetime.js +132 -92
  9. package/components/ion-item-sliding.js +1 -1
  10. package/components/ion-menu.js +1 -1
  11. package/components/ion-modal.js +2 -2
  12. package/components/ion-nav.js +1 -1
  13. package/components/ion-range.js +3 -3
  14. package/components/ion-refresher.js +3 -3
  15. package/components/ion-reorder-group.js +1 -1
  16. package/components/ion-router-outlet.js +1 -1
  17. package/components/ion-segment.js +1 -1
  18. package/components/ion-toggle.js +7 -4
  19. package/components/ios.transition.js +1 -1
  20. package/components/md.transition.js +1 -1
  21. package/components/notch-controller.js +1 -1
  22. package/components/picker-column.js +1 -1
  23. package/components/popover.js +1 -1
  24. package/components/radio.js +6 -3
  25. package/components/swipe-back.js +1 -1
  26. package/css/core.css.map +1 -1
  27. package/css/ionic.bundle.css.map +1 -1
  28. package/dist/cjs/{data-967b9c15.js → data-db832785.js} +31 -7
  29. package/dist/cjs/index.cjs.js +10 -9
  30. package/dist/cjs/ion-checkbox.cjs.entry.js +5 -3
  31. package/dist/cjs/ion-datetime-button.cjs.entry.js +4 -1
  32. package/dist/cjs/ion-datetime_3.cjs.entry.js +132 -93
  33. package/dist/cjs/ion-input.cjs.entry.js +1 -1
  34. package/dist/cjs/ion-radio_2.cjs.entry.js +5 -3
  35. package/dist/cjs/ion-range.cjs.entry.js +2 -2
  36. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  37. package/dist/cjs/ion-textarea.cjs.entry.js +1 -1
  38. package/dist/cjs/ion-toggle.cjs.entry.js +5 -3
  39. package/dist/cjs/ionic.cjs.js +1 -1
  40. package/dist/cjs/loader.cjs.js +1 -1
  41. package/dist/cjs/{notch-controller-c27fe0b7.js → notch-controller-f4f6af5d.js} +1 -1
  42. package/dist/collection/components/checkbox/checkbox.ios.css +67 -0
  43. package/dist/collection/components/checkbox/checkbox.js +24 -4
  44. package/dist/collection/components/checkbox/checkbox.md.css +67 -0
  45. package/dist/collection/components/checkbox/test/item/checkbox.e2e.js +13 -0
  46. package/dist/collection/components/checkbox/test/label/checkbox.e2e.js +27 -0
  47. package/dist/collection/components/datetime/datetime.ios.css +32 -29
  48. package/dist/collection/components/datetime/datetime.js +137 -93
  49. package/dist/collection/components/datetime/datetime.md.css +20 -28
  50. package/dist/collection/components/datetime/test/basic/datetime.e2e.js +25 -0
  51. package/dist/collection/components/datetime/test/color/datetime.e2e.js +1 -1
  52. package/dist/collection/components/datetime/test/custom/datetime.e2e.js +27 -1
  53. package/dist/collection/components/datetime/test/prefer-wheel/datetime.e2e.js +15 -0
  54. package/dist/collection/components/datetime/test/set-value/datetime.e2e.js +9 -12
  55. package/dist/collection/components/datetime/utils/data.js +14 -6
  56. package/dist/collection/components/datetime/utils/parse.js +18 -1
  57. package/dist/collection/components/datetime-button/datetime-button.js +3 -0
  58. package/dist/collection/components/radio/radio.ios.css +67 -0
  59. package/dist/collection/components/radio/radio.js +24 -4
  60. package/dist/collection/components/radio/radio.md.css +67 -0
  61. package/dist/collection/components/radio/test/item/radio.e2e.js +15 -0
  62. package/dist/collection/components/radio/test/label-placement/radio.e2e.js +31 -0
  63. package/dist/collection/components/range/range.ios.css +66 -1
  64. package/dist/collection/components/range/range.js +3 -3
  65. package/dist/collection/components/range/range.md.css +66 -1
  66. package/dist/collection/components/range/test/item/range.e2e.js +15 -0
  67. package/dist/collection/components/range/test/label/range.e2e.js +59 -0
  68. package/dist/collection/components/toggle/test/item/toggle.e2e.js +15 -0
  69. package/dist/collection/components/toggle/test/label/toggle.e2e.js +27 -0
  70. package/dist/collection/components/toggle/toggle.ios.css +67 -1
  71. package/dist/collection/components/toggle/toggle.js +24 -4
  72. package/dist/collection/components/toggle/toggle.md.css +67 -1
  73. package/dist/collection/index.js +1 -0
  74. package/dist/collection/utils/forms/notch-controller.js +1 -1
  75. package/dist/docs.json +147 -18
  76. package/dist/esm/{data-4b448e3a.js → data-009dbf15.js} +31 -7
  77. package/dist/esm/index.js +2 -2
  78. package/dist/esm/ion-checkbox.entry.js +5 -3
  79. package/dist/esm/ion-datetime-button.entry.js +4 -1
  80. package/dist/esm/ion-datetime_3.entry.js +132 -93
  81. package/dist/esm/ion-input.entry.js +1 -1
  82. package/dist/esm/ion-radio_2.entry.js +5 -3
  83. package/dist/esm/ion-range.entry.js +2 -2
  84. package/dist/esm/ion-select_3.entry.js +1 -1
  85. package/dist/esm/ion-textarea.entry.js +1 -1
  86. package/dist/esm/ion-toggle.entry.js +5 -3
  87. package/dist/esm/ionic.js +1 -1
  88. package/dist/esm/loader.js +1 -1
  89. package/dist/esm/{notch-controller-cb5bbe6a.js → notch-controller-8c9c0e54.js} +1 -1
  90. package/dist/esm-es5/data-009dbf15.js +4 -0
  91. package/dist/esm-es5/index.js +1 -1
  92. package/dist/esm-es5/ion-checkbox.entry.js +1 -1
  93. package/dist/esm-es5/ion-datetime-button.entry.js +1 -1
  94. package/dist/esm-es5/ion-datetime_3.entry.js +1 -1
  95. package/dist/esm-es5/ion-input.entry.js +1 -1
  96. package/dist/esm-es5/ion-radio_2.entry.js +1 -1
  97. package/dist/esm-es5/ion-range.entry.js +1 -1
  98. package/dist/esm-es5/ion-select_3.entry.js +1 -1
  99. package/dist/esm-es5/ion-textarea.entry.js +1 -1
  100. package/dist/esm-es5/ion-toggle.entry.js +1 -1
  101. package/dist/esm-es5/ionic.js +1 -1
  102. package/dist/esm-es5/loader.js +1 -1
  103. package/dist/html.html-data.json +52 -4
  104. package/dist/ionic/index.esm.js +1 -1
  105. package/dist/ionic/ionic.esm.js +1 -1
  106. package/dist/ionic/{p-dc4c9086.system.entry.js → p-264b5dd9.system.entry.js} +1 -1
  107. package/dist/ionic/{p-4d2807fa.entry.js → p-37c9e295.entry.js} +1 -1
  108. package/dist/ionic/p-3d575698.system.entry.js +4 -0
  109. package/dist/ionic/p-44ef7224.system.js +4 -0
  110. package/dist/ionic/{p-1c236453.system.entry.js → p-46058a1e.system.entry.js} +1 -1
  111. package/dist/ionic/{p-68deb933.entry.js → p-52edd6b0.entry.js} +1 -1
  112. package/dist/ionic/p-60f22efc.system.entry.js +4 -0
  113. package/dist/ionic/p-6d640b2b.entry.js +4 -0
  114. package/dist/ionic/p-74ef17ea.system.entry.js +4 -0
  115. package/dist/ionic/p-7cfd253c.js +4 -0
  116. package/dist/ionic/p-9fccec52.system.js +4 -0
  117. package/dist/ionic/p-aa702daf.entry.js +4 -0
  118. package/dist/ionic/p-ab051966.entry.js +4 -0
  119. package/dist/ionic/p-c005eb67.system.entry.js +4 -0
  120. package/dist/ionic/p-c68a8814.system.js +1 -1
  121. package/dist/ionic/p-cc758251.system.entry.js +4 -0
  122. package/dist/ionic/{p-0ce43a8f.system.entry.js → p-d0d09178.system.entry.js} +1 -1
  123. package/dist/ionic/p-dfee12f6.entry.js +4 -0
  124. package/dist/ionic/{p-8db97b90.entry.js → p-e3ca182a.entry.js} +1 -1
  125. package/dist/ionic/p-f0c28fbd.entry.js +4 -0
  126. package/dist/ionic/p-fdfc7b98.entry.js +4 -0
  127. package/dist/ionic/p-ff0611e6.system.entry.js +4 -0
  128. package/dist/types/components/checkbox/checkbox.d.ts +8 -1
  129. package/dist/types/components/datetime/datetime.d.ts +23 -10
  130. package/dist/types/components/datetime/utils/data.d.ts +1 -1
  131. package/dist/types/components/datetime/utils/parse.d.ts +3 -3
  132. package/dist/types/components/radio/radio.d.ts +8 -1
  133. package/dist/types/components/range/range.d.ts +2 -1
  134. package/dist/types/components/toast/toast-interface.d.ts +3 -0
  135. package/dist/types/components/toggle/toggle.d.ts +8 -1
  136. package/dist/types/components.d.ts +40 -16
  137. package/dist/types/index.d.ts +1 -0
  138. package/dist/types/interface.d.ts +1 -0
  139. package/hydrate/index.js +193 -111
  140. package/package.json +1 -1
  141. package/dist/esm-es5/data-4b448e3a.js +0 -4
  142. package/dist/ionic/p-17e961cf.entry.js +0 -4
  143. package/dist/ionic/p-1b533a76.system.entry.js +0 -4
  144. package/dist/ionic/p-44a6f15d.system.entry.js +0 -4
  145. package/dist/ionic/p-4f7109fa.entry.js +0 -4
  146. package/dist/ionic/p-601fd53c.js +0 -4
  147. package/dist/ionic/p-7106c152.system.js +0 -4
  148. package/dist/ionic/p-9eaaa910.entry.js +0 -4
  149. package/dist/ionic/p-af6125d1.system.entry.js +0 -4
  150. package/dist/ionic/p-b8d7f0eb.system.entry.js +0 -4
  151. package/dist/ionic/p-b9179eab.entry.js +0 -4
  152. package/dist/ionic/p-cc8910e7.system.js +0 -4
  153. package/dist/ionic/p-dc7d392d.entry.js +0 -4
  154. package/dist/ionic/p-e65aed58.system.entry.js +0 -4
  155. package/dist/ionic/p-f3ec1f2b.entry.js +0 -4
  156. package/dist/ionic/p-fb26fa56.system.entry.js +0 -4
  157. /package/dist/esm-es5/{notch-controller-cb5bbe6a.js → notch-controller-8c9c0e54.js} +0 -0
  158. /package/dist/ionic/{p-4b37d92d.js → p-3bad5c1a.js} +0 -0
  159. /package/dist/ionic/{p-46b2991d.system.js → p-ab4cff27.system.js} +0 -0
@@ -33,16 +33,17 @@ import { getCalendarDayState, getHighlightStyles, isDayDisabled, isMonthDisabled
33
33
  *
34
34
  * @part month-year-button - The button that opens the month/year picker when
35
35
  * using a grid style layout.
36
+ *
37
+ * @part calendar-day - The individual buttons that display a day inside of the datetime
38
+ * calendar.
39
+ * @part calendar-day active - The currently selected calendar day.
40
+ * @part calendar-day today - The calendar day that contains the current day.
41
+ * @part calendar-day disabled - The calendar day that is disabled.
36
42
  */
37
43
  export class Datetime {
38
44
  constructor() {
39
45
  this.inputId = `ion-dt-${datetimeIds++}`;
40
46
  this.prevPresentation = null;
41
- /**
42
- * Duplicate reference to `activeParts` that does not trigger a re-render of the component.
43
- * Allows caching an instance of the `activeParts` in between render cycles.
44
- */
45
- this.activePartsClone = [];
46
47
  this.warnIfIncorrectValueUsage = () => {
47
48
  const { multiple, value } = this;
48
49
  if (!multiple && Array.isArray(value)) {
@@ -70,9 +71,9 @@ export class Datetime {
70
71
  * data. This should be used when rendering an
71
72
  * interface in an environment where the `value`
72
73
  * may not be set. This function works
73
- * by returning the first selected date in
74
- * "activePartsClone" and then falling back to
75
- * defaultParts if no active date is selected.
74
+ * by returning the first selected date and then
75
+ * falling back to defaultParts if no active date
76
+ * is selected.
76
77
  */
77
78
  this.getActivePartsWithFallback = () => {
78
79
  var _a;
@@ -80,8 +81,8 @@ export class Datetime {
80
81
  return (_a = this.getActivePart()) !== null && _a !== void 0 ? _a : defaultParts;
81
82
  };
82
83
  this.getActivePart = () => {
83
- const { activePartsClone } = this;
84
- return Array.isArray(activePartsClone) ? activePartsClone[0] : activePartsClone;
84
+ const { activeParts } = this;
85
+ return Array.isArray(activeParts) ? activeParts[0] : activeParts;
85
86
  };
86
87
  this.closeParentOverlay = () => {
87
88
  const popoverOrModal = this.el.closest('ion-modal, ion-popover');
@@ -93,7 +94,7 @@ export class Datetime {
93
94
  this.workingParts = Object.assign({}, parts);
94
95
  };
95
96
  this.setActiveParts = (parts, removeDate = false) => {
96
- const { multiple, minParts, maxParts, activePartsClone } = this;
97
+ const { multiple, minParts, maxParts, activeParts } = this;
97
98
  /**
98
99
  * When setting the active parts, it is possible
99
100
  * to set invalid data. For example,
@@ -107,16 +108,7 @@ export class Datetime {
107
108
  const validatedParts = validateParts(parts, minParts, maxParts);
108
109
  this.setWorkingParts(validatedParts);
109
110
  if (multiple) {
110
- /**
111
- * We read from activePartsClone here because valueChanged() only updates that,
112
- * so it's the more reliable source of truth. If we read from activeParts, then
113
- * if you click July 1, manually set the value to July 2, and then click July 3,
114
- * the new value would be [July 1, July 3], ignoring the value set.
115
- *
116
- * We can then pass the new value to activeParts (rather than activePartsClone)
117
- * since the clone will be updated automatically by activePartsChanged().
118
- */
119
- const activePartsArray = Array.isArray(activePartsClone) ? activePartsClone : [activePartsClone];
111
+ const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
120
112
  if (removeDate) {
121
113
  this.activeParts = activePartsArray.filter((p) => !isSameDay(p, validatedParts));
122
114
  }
@@ -241,7 +233,7 @@ export class Datetime {
241
233
  /**
242
234
  * Get the number of padding days so
243
235
  * we know how much to offset our next selector by
244
- * to grab the correct calenday-day element.
236
+ * to grab the correct calendar-day element.
245
237
  */
246
238
  const padding = currentMonth.querySelectorAll('.calendar-day-padding');
247
239
  const { day } = this.workingParts;
@@ -252,7 +244,7 @@ export class Datetime {
252
244
  * Get the calendar day element
253
245
  * and focus it.
254
246
  */
255
- const dayEl = currentMonth.querySelector(`.calendar-day:nth-of-type(${padding.length + day})`);
247
+ const dayEl = currentMonth.querySelector(`.calendar-day-wrapper:nth-of-type(${padding.length + day}) .calendar-day`);
256
248
  if (dayEl) {
257
249
  dayEl.focus();
258
250
  }
@@ -333,6 +325,19 @@ export class Datetime {
333
325
  const monthBox = month.getBoundingClientRect();
334
326
  if (Math.abs(monthBox.x - box.x) > 2)
335
327
  return;
328
+ /**
329
+ * If we're force-rendering a month, assume we've
330
+ * scrolled to that and return it.
331
+ *
332
+ * If forceRenderDate is ever used in a context where the
333
+ * forced month is not immediately auto-scrolled to, this
334
+ * should be updated to also check whether `month` has the
335
+ * same month and year as the forced date.
336
+ */
337
+ const { forceRenderDate } = this;
338
+ if (forceRenderDate !== undefined) {
339
+ return { month: forceRenderDate.month, year: forceRenderDate.year, day: forceRenderDate.day };
340
+ }
336
341
  /**
337
342
  * From here, we can determine if the start
338
343
  * month or the end month was scrolled into view.
@@ -387,6 +392,9 @@ export class Datetime {
387
392
  this.setWorkingParts(Object.assign(Object.assign({}, this.workingParts), { month, day: day, year }));
388
393
  calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
389
394
  calendarBodyRef.style.removeProperty('overflow');
395
+ if (this.resolveForceDateScrolling) {
396
+ this.resolveForceDateScrolling();
397
+ }
390
398
  });
391
399
  };
392
400
  /**
@@ -441,10 +449,17 @@ export class Datetime {
441
449
  }
442
450
  };
443
451
  this.processValue = (value) => {
444
- const hasValue = value !== null && value !== undefined;
452
+ const hasValue = value !== null && value !== undefined && (!Array.isArray(value) || value.length > 0);
445
453
  const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
446
- const { minParts, maxParts } = this;
454
+ const { minParts, maxParts, workingParts, el } = this;
447
455
  this.warnIfIncorrectValueUsage();
456
+ /**
457
+ * Return early if the value wasn't parsed correctly, such as
458
+ * if an improperly formatted date string was provided.
459
+ */
460
+ if (!valueToProcess) {
461
+ return;
462
+ }
448
463
  /**
449
464
  * Datetime should only warn of out of bounds values
450
465
  * if set by the user. If the `value` is undefined,
@@ -462,16 +477,9 @@ export class Datetime {
462
477
  * that the values don't necessarily have to be in order.
463
478
  */
464
479
  const singleValue = Array.isArray(valueToProcess) ? valueToProcess[0] : valueToProcess;
465
- const { month, day, year, hour, minute } = clampDate(singleValue, minParts, maxParts);
480
+ const targetValue = clampDate(singleValue, minParts, maxParts);
481
+ const { month, day, year, hour, minute } = targetValue;
466
482
  const ampm = parseAmPm(hour);
467
- this.setWorkingParts({
468
- month,
469
- day,
470
- year,
471
- hour,
472
- minute,
473
- ampm,
474
- });
475
483
  /**
476
484
  * Since `activeParts` indicates a value that
477
485
  * been explicitly selected either by the
@@ -501,6 +509,62 @@ export class Datetime {
501
509
  */
502
510
  this.activeParts = [];
503
511
  }
512
+ /**
513
+ * Only animate if:
514
+ * 1. We're using grid style (wheel style pickers should just jump to new value)
515
+ * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
516
+ * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
517
+ * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
518
+ */
519
+ const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
520
+ const bodyIsVisible = el.classList.contains('datetime-ready');
521
+ const { isGridStyle, showMonthAndYear } = this;
522
+ if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
523
+ this.animateToDate(targetValue);
524
+ }
525
+ else {
526
+ /**
527
+ * We only need to do this if we didn't just animate to a new month,
528
+ * since that calls prevMonth/nextMonth which calls setWorkingParts for us.
529
+ */
530
+ this.setWorkingParts({
531
+ month,
532
+ day,
533
+ year,
534
+ hour,
535
+ minute,
536
+ ampm,
537
+ });
538
+ }
539
+ };
540
+ this.animateToDate = async (targetValue) => {
541
+ const { workingParts } = this;
542
+ /**
543
+ * Tell other render functions that we need to force the
544
+ * target month to appear in place of the actual next/prev month.
545
+ * Because this is a State variable, a rerender will be triggered
546
+ * automatically, updating the rendered months.
547
+ */
548
+ this.forceRenderDate = targetValue;
549
+ /**
550
+ * Flag that we've started scrolling to the forced date.
551
+ * The resolve function will be called by the datetime's
552
+ * scroll listener when it's done updating everything.
553
+ * This is a replacement for making prev/nextMonth async,
554
+ * since the logic we're waiting on is in a listener.
555
+ */
556
+ const forceDateScrollingPromise = new Promise((resolve) => {
557
+ this.resolveForceDateScrolling = resolve;
558
+ });
559
+ /**
560
+ * Animate smoothly to the forced month. This will also update
561
+ * workingParts and correct the surrounding months for us.
562
+ */
563
+ const targetMonthIsBefore = isBefore(targetValue, workingParts);
564
+ targetMonthIsBefore ? this.prevMonth() : this.nextMonth();
565
+ await forceDateScrollingPromise;
566
+ this.resolveForceDateScrolling = undefined;
567
+ this.forceRenderDate = undefined;
504
568
  };
505
569
  this.onFocus = () => {
506
570
  this.ionFocus.emit();
@@ -556,6 +620,7 @@ export class Datetime {
556
620
  ampm: 'pm',
557
621
  };
558
622
  this.isTimePopoverOpen = false;
623
+ this.forceRenderDate = undefined;
559
624
  this.color = 'primary';
560
625
  this.name = this.inputId;
561
626
  this.disabled = false;
@@ -595,6 +660,11 @@ export class Datetime {
595
660
  maxChanged() {
596
661
  this.processMaxParts();
597
662
  }
663
+ get isGridStyle() {
664
+ const { presentation, preferWheel } = this;
665
+ const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
666
+ return hasDatePresentation && !preferWheel;
667
+ }
598
668
  yearValuesChanged() {
599
669
  this.parsedYearValues = convertToArrayOfNumbers(this.yearValues);
600
670
  }
@@ -610,51 +680,13 @@ export class Datetime {
610
680
  minuteValuesChanged() {
611
681
  this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues);
612
682
  }
613
- activePartsChanged() {
614
- this.activePartsClone = this.activeParts;
615
- }
616
683
  /**
617
684
  * Update the datetime value when the value changes
618
685
  */
619
- valueChanged() {
620
- const { value, minParts, maxParts, workingParts } = this;
686
+ async valueChanged() {
687
+ const { value } = this;
621
688
  if (this.hasValue()) {
622
- this.warnIfIncorrectValueUsage();
623
- /**
624
- * Clones the value of the `activeParts` to the private clone, to update
625
- * the date display on the current render cycle without causing another render.
626
- *
627
- * This allows us to update the current value's date/time display without
628
- * refocusing or shifting the user's display (leaves the user in place).
629
- */
630
- const valueDateParts = parseDate(value);
631
- if (valueDateParts) {
632
- warnIfValueOutOfBounds(valueDateParts, minParts, maxParts);
633
- if (Array.isArray(valueDateParts)) {
634
- this.activePartsClone = [...valueDateParts];
635
- }
636
- else {
637
- const { month, day, year, hour, minute } = valueDateParts;
638
- const ampm = hour != null ? (hour >= 12 ? 'pm' : 'am') : undefined;
639
- this.activePartsClone = Object.assign(Object.assign({}, this.activeParts), { month,
640
- day,
641
- year,
642
- hour,
643
- minute,
644
- ampm });
645
- /**
646
- * The working parts am/pm value must be updated when the value changes, to
647
- * ensure the time picker hour column values are generated correctly.
648
- *
649
- * Note that we don't need to do this if valueDateParts is an array, since
650
- * multiple="true" does not apply to time pickers.
651
- */
652
- this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { ampm }));
653
- }
654
- }
655
- else {
656
- printIonWarning(`Unable to parse date string: ${value}. Please provide a valid ISO 8601 datetime string.`);
657
- }
689
+ this.processValue(value);
658
690
  }
659
691
  this.emitStyle();
660
692
  this.ionValueChange.emit({ value });
@@ -857,8 +889,6 @@ export class Datetime {
857
889
  printIonWarning('The highlightedDates property is not supported with preferWheel="true".', el);
858
890
  }
859
891
  }
860
- this.processMinParts();
861
- this.processMaxParts();
862
892
  const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues));
863
893
  const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues));
864
894
  const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues));
@@ -866,6 +896,8 @@ export class Datetime {
866
896
  const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
867
897
  const todayParts = (this.todayParts = parseDate(getToday()));
868
898
  this.defaultParts = getClosestValidDate(todayParts, monthValues, dayValues, yearValues, hourValues, minuteValues);
899
+ this.processMinParts();
900
+ this.processMaxParts();
869
901
  this.processValue(this.value);
870
902
  this.emitStyle();
871
903
  }
@@ -1250,7 +1282,7 @@ export class Datetime {
1250
1282
  const { el, highlightedDates, isDateEnabled, multiple } = this;
1251
1283
  const referenceParts = { month, day, year };
1252
1284
  const isCalendarPadding = day === null;
1253
- const { isActive, isToday, ariaLabel, ariaSelected, disabled, text } = getCalendarDayState(this.locale, referenceParts, this.activePartsClone, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues);
1285
+ const { isActive, isToday, ariaLabel, ariaSelected, disabled, text } = getCalendarDayState(this.locale, referenceParts, this.activeParts, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues);
1254
1286
  const dateIsoString = convertDataToISO(referenceParts);
1255
1287
  let isCalDayDisabled = isCalMonthDisabled || disabled;
1256
1288
  if (!isCalDayDisabled && isDateEnabled !== undefined) {
@@ -1274,14 +1306,30 @@ export class Datetime {
1274
1306
  if (highlightedDates !== undefined && !isActive && day !== null) {
1275
1307
  dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
1276
1308
  }
1277
- return (h("button", { tabindex: "-1", "data-day": day, "data-month": month, "data-year": year, "data-index": index, "data-day-of-week": dayOfWeek, disabled: isCalDayDisabled, class: {
1309
+ let dateParts = undefined;
1310
+ // "Filler days" at the beginning of the grid should not get the calendar day
1311
+ // CSS parts added to them
1312
+ if (!isCalendarPadding) {
1313
+ dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${isCalDayDisabled ? ' disabled' : ''}`;
1314
+ }
1315
+ return (h("div", { class: "calendar-day-wrapper" }, h("button", {
1316
+ // We need to use !important for the inline styles here because
1317
+ // otherwise the CSS shadow parts will override these styles.
1318
+ // See https://github.com/WICG/webcomponents/issues/847
1319
+ // Both the CSS shadow parts and highlightedDates styles are
1320
+ // provided by the developer, but highlightedDates styles should
1321
+ // always take priority.
1322
+ ref: (el) => {
1323
+ if (el) {
1324
+ el.style.setProperty('color', `${dateStyle ? dateStyle.textColor : ''}`, 'important');
1325
+ el.style.setProperty('background-color', `${dateStyle ? dateStyle.backgroundColor : ''}`, 'important');
1326
+ }
1327
+ }, tabindex: "-1", "data-day": day, "data-month": month, "data-year": year, "data-index": index, "data-day-of-week": dayOfWeek, disabled: isCalDayDisabled, class: {
1278
1328
  'calendar-day-padding': isCalendarPadding,
1279
1329
  'calendar-day': true,
1280
1330
  'calendar-day-active': isActive,
1281
1331
  'calendar-day-today': isToday,
1282
- }, style: dateStyle && {
1283
- color: dateStyle.textColor,
1284
- }, "aria-hidden": isCalendarPadding ? 'true' : null, "aria-selected": ariaSelected, "aria-label": ariaLabel, onClick: () => {
1332
+ }, part: dateParts, "aria-hidden": isCalendarPadding ? 'true' : null, "aria-selected": ariaSelected, "aria-label": ariaLabel, onClick: () => {
1285
1333
  if (isCalendarPadding) {
1286
1334
  return;
1287
1335
  }
@@ -1301,13 +1349,12 @@ export class Datetime {
1301
1349
  day,
1302
1350
  year }));
1303
1351
  }
1304
- } }, h("div", { class: "calendar-day-highlight", style: {
1305
- backgroundColor: dateStyle === null || dateStyle === void 0 ? void 0 : dateStyle.backgroundColor,
1306
- } }), text));
1352
+ }
1353
+ }, text)));
1307
1354
  }))));
1308
1355
  }
1309
1356
  renderCalendarBody() {
1310
- return (h("div", { class: "calendar-body ion-focusable", ref: (el) => (this.calendarBodyRef = el), tabindex: "0" }, generateMonths(this.workingParts).map(({ month, year }) => {
1357
+ return (h("div", { class: "calendar-body ion-focusable", ref: (el) => (this.calendarBodyRef = el), tabindex: "0" }, generateMonths(this.workingParts, this.forceRenderDate).map(({ month, year }) => {
1311
1358
  return this.renderMonth(month, year);
1312
1359
  })));
1313
1360
  }
@@ -1462,14 +1509,13 @@ export class Datetime {
1462
1509
  }
1463
1510
  }
1464
1511
  render() {
1465
- const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size } = this;
1512
+ const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size, isGridStyle, } = this;
1466
1513
  const mode = getIonMode(this);
1467
1514
  const isMonthAndYearPresentation = presentation === 'year' || presentation === 'month' || presentation === 'month-year';
1468
1515
  const shouldShowMonthAndYear = showMonthAndYear || isMonthAndYearPresentation;
1469
1516
  const monthYearPickerOpen = showMonthAndYear && !isMonthAndYearPresentation;
1470
1517
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1471
1518
  const hasWheelVariant = hasDatePresentation && preferWheel;
1472
- const hasGrid = hasDatePresentation && !preferWheel;
1473
1519
  renderHiddenInput(true, el, name, formatValue(value), disabled);
1474
1520
  return (h(Host, { "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1475
1521
  [mode]: true,
@@ -1480,7 +1526,7 @@ export class Datetime {
1480
1526
  [`datetime-presentation-${presentation}`]: true,
1481
1527
  [`datetime-size-${size}`]: true,
1482
1528
  [`datetime-prefer-wheel`]: hasWheelVariant,
1483
- [`datetime-grid`]: hasGrid,
1529
+ [`datetime-grid`]: isGridStyle,
1484
1530
  })) }, this.renderDatetime(mode)));
1485
1531
  }
1486
1532
  static get is() { return "ion-datetime"; }
@@ -2039,7 +2085,8 @@ export class Datetime {
2039
2085
  "showMonthAndYear": {},
2040
2086
  "activeParts": {},
2041
2087
  "workingParts": {},
2042
- "isTimePopoverOpen": {}
2088
+ "isTimePopoverOpen": {},
2089
+ "forceRenderDate": {}
2043
2090
  };
2044
2091
  }
2045
2092
  static get events() {
@@ -2267,9 +2314,6 @@ export class Datetime {
2267
2314
  }, {
2268
2315
  "propName": "minuteValues",
2269
2316
  "methodName": "minuteValuesChanged"
2270
- }, {
2271
- "propName": "activeParts",
2272
- "methodName": "activePartsChanged"
2273
2317
  }, {
2274
2318
  "propName": "value",
2275
2319
  "methodName": "valueChanged"
@@ -281,6 +281,8 @@ ion-picker-column-internal {
281
281
  }
282
282
 
283
283
  :host .calendar-body .calendar-month {
284
+ display: flex;
285
+ flex-flow: column;
284
286
  /**
285
287
  * Swiping should snap to at
286
288
  * most one month at a time.
@@ -314,11 +316,21 @@ ion-picker-column-internal {
314
316
  grid-template-columns: repeat(7, 1fr);
315
317
  }
316
318
 
319
+ :host .calendar-day-wrapper {
320
+ display: flex;
321
+ align-items: center;
322
+ justify-content: center;
323
+ min-width: 0;
324
+ min-height: 0;
325
+ overflow: visible;
326
+ }
327
+
317
328
  /**
318
329
  * Center the day text vertically
319
330
  * and horizontally within its grid cell.
320
331
  */
321
332
  :host .calendar-day {
333
+ border-radius: 50%;
322
334
  -webkit-padding-start: 0px;
323
335
  padding-inline-start: 0px;
324
336
  -webkit-padding-end: 0px;
@@ -350,18 +362,9 @@ ion-picker-column-internal {
350
362
  opacity: 0.4;
351
363
  }
352
364
 
353
- .calendar-day-highlight {
354
- border-radius: 32px;
355
- -webkit-padding-start: 4px;
356
- padding-inline-start: 4px;
357
- -webkit-padding-end: 4px;
358
- padding-inline-end: 4px;
359
- padding-top: 4px;
360
- padding-bottom: 4px;
361
- position: absolute;
362
- width: 32px;
363
- height: 32px;
364
- z-index: -1;
365
+ .calendar-day:focus {
366
+ background: rgba(var(--ion-color-base-rgb), 0.2);
367
+ box-shadow: 0px 0px 0px 4px rgba(var(--ion-color-base-rgb), 0.2);
365
368
  }
366
369
 
367
370
  :host .datetime-time {
@@ -480,30 +483,19 @@ ion-picker-column-internal {
480
483
  }
481
484
 
482
485
  :host .calendar-day {
483
- -webkit-padding-start: 0px;
484
- padding-inline-start: 0px;
485
- -webkit-padding-end: 0;
486
- padding-inline-end: 0;
487
- padding-top: 13px;
488
- padding-bottom: 13px;
486
+ width: 42px;
487
+ min-width: 42px;
488
+ height: 42px;
489
489
  font-size: 14px;
490
490
  }
491
491
 
492
- .calendar-day:focus .calendar-day-highlight {
493
- background: rgba(var(--ion-color-base-rgb), 0.2);
494
- box-shadow: 0px 0px 0px 4px rgba(var(--ion-color-base-rgb), 0.2);
495
- }
496
-
497
492
  /**
498
493
  * Day that today but not selected
499
494
  * should have ion-color for text color.
500
495
  */
501
496
  :host .calendar-day.calendar-day-today {
502
- color: var(--ion-color-base);
503
- }
504
-
505
- .calendar-day.calendar-day-today .calendar-day-highlight {
506
497
  border: 1px solid var(--ion-color-base);
498
+ color: var(--ion-color-base);
507
499
  }
508
500
 
509
501
  /**
@@ -515,7 +507,7 @@ ion-picker-column-internal {
515
507
  color: var(--ion-color-contrast);
516
508
  }
517
509
 
518
- .calendar-day.calendar-day-active .calendar-day-highlight {
510
+ .calendar-day.calendar-day-active {
519
511
  border: 1px solid var(--ion-color-base);
520
512
  background: var(--ion-color-base);
521
513
  }
@@ -348,3 +348,28 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
348
348
  });
349
349
  });
350
350
  });
351
+ /**
352
+ * This behavior does not differ across
353
+ * directions.
354
+ */
355
+ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
356
+ test.describe(title('datetime: focus'), () => {
357
+ test('should focus the selected day and then the day after', async ({ page }) => {
358
+ await page.setContent(`
359
+ <ion-datetime value="2023-08-01"></ion-datetime>
360
+ `, config);
361
+ await page.waitForSelector('.datetime-ready');
362
+ const datetime = page.locator('ion-datetime');
363
+ const day = datetime.locator(`.calendar-day[data-day='1'][data-month='8']`);
364
+ await day.focus();
365
+ await page.waitForChanges();
366
+ await expect(day).toBeFocused();
367
+ await expect(datetime).toHaveScreenshot(screenshot(`datetime-focus-selected-calendar-day`));
368
+ await page.keyboard.press('ArrowRight');
369
+ await page.waitForChanges();
370
+ const nextDay = datetime.locator(`.calendar-day[data-day='2'][data-month='8']`);
371
+ await expect(nextDay).toBeFocused();
372
+ await expect(datetime).toHaveScreenshot(screenshot(`datetime-focus-calendar-day`));
373
+ });
374
+ });
375
+ });
@@ -10,7 +10,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
10
10
  test.describe(title('datetime: color'), () => {
11
11
  test('should not have visual regressions', async ({ page }) => {
12
12
  await page.goto('/src/components/datetime/test/color', config);
13
- const datetime = page.locator('ion-datetime');
13
+ const datetime = page.locator('#color-datetime');
14
14
  await expect(datetime).toHaveScreenshot(screenshot(`datetime-color`));
15
15
  await page.evaluate(() => document.body.classList.toggle('dark'));
16
16
  await page.waitForChanges();
@@ -13,7 +13,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
13
13
  await expect(datetime).toHaveScreenshot(screenshot(`datetime-custom-wheel`));
14
14
  });
15
15
  test('should allow styling time picker in grid style datetimes', async ({ page }) => {
16
- const timeButton = page.locator('ion-datetime .time-body');
16
+ const timeButton = page.locator('#custom-grid .time-body');
17
17
  const popover = page.locator('.popover-viewport');
18
18
  const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
19
19
  await expect(timeButton).toHaveScreenshot(screenshot(`datetime-custom-time-button`));
@@ -22,5 +22,31 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
22
22
  await expect(popover).toHaveScreenshot(screenshot(`datetime-custom-time-picker`));
23
23
  await expect(timeButton).toHaveScreenshot(screenshot(`datetime-custom-time-button-active`));
24
24
  });
25
+ test('should allow styling calendar days in grid style datetimes', async ({ page }) => {
26
+ const datetime = page.locator('#custom-calendar-days');
27
+ await expect(datetime).toHaveScreenshot(screenshot(`datetime-custom-calendar-days`));
28
+ });
29
+ });
30
+ });
31
+ /**
32
+ * This behavior does not differ across
33
+ * directions.
34
+ */
35
+ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
36
+ test.describe(title('datetime: custom focus'), () => {
37
+ test('should focus the selected day and then the day after', async ({ page }) => {
38
+ await page.goto(`/src/components/datetime/test/custom`, config);
39
+ const datetime = page.locator('#custom-calendar-days');
40
+ const day = datetime.locator(`.calendar-day[data-day='15'][data-month='6']`);
41
+ await day.focus();
42
+ await page.waitForChanges();
43
+ await expect(day).toBeFocused();
44
+ await expect(datetime).toHaveScreenshot(screenshot(`datetime-custom-focus-selected-calendar-day`));
45
+ await page.keyboard.press('ArrowRight');
46
+ await page.waitForChanges();
47
+ const nextDay = datetime.locator(`.calendar-day[data-day='16'][data-month='6']`);
48
+ await expect(nextDay).toBeFocused();
49
+ await expect(datetime).toHaveScreenshot(screenshot(`datetime-custom-focus-calendar-day`));
50
+ });
25
51
  });
26
52
  });
@@ -184,6 +184,21 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
184
184
  await yearValues.nth(10).click();
185
185
  await ionChange.next();
186
186
  });
187
+ test('should jump to selected date when programmatically updating value', async ({ page }) => {
188
+ await page.setContent(`
189
+ <ion-datetime presentation="date" prefer-wheel="true" min="2019-05-05" max="2023-10-01" value="2019-05-30"></ion-datetime>
190
+ `, config);
191
+ await page.waitForSelector('.datetime-ready');
192
+ const datetime = page.locator('ion-datetime');
193
+ await datetime.evaluate((el) => (el.value = '2021-05-25T12:40:00.000Z'));
194
+ await page.waitForChanges();
195
+ const selectedMonth = datetime.locator('.month-column .picker-item-active');
196
+ const selectedDay = datetime.locator('.day-column .picker-item-active');
197
+ const selectedYear = datetime.locator('.year-column .picker-item-active');
198
+ await expect(selectedMonth).toHaveText(/May/);
199
+ await expect(selectedDay).toHaveText(/25/);
200
+ await expect(selectedYear).toHaveText(/2021/);
201
+ });
187
202
  test.describe('datetime: date wheel localization', () => {
188
203
  test('should correctly localize the date data', async ({ page }) => {
189
204
  await page.setContent(`
@@ -30,24 +30,21 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
30
30
  await page.waitForSelector('.datetime-ready');
31
31
  const datetime = page.locator('ion-datetime');
32
32
  const activeDayButton = page.locator('.calendar-day-active');
33
- const monthYearButton = page.locator('.calendar-month-year');
34
- const monthColumn = page.locator('.month-column');
35
- const ionChange = await page.spyOnEvent('ionChange');
36
33
  await datetime.evaluate((el) => (el.value = '2021-10-05'));
37
- // Open month/year picker
38
- await monthYearButton.click();
39
- await page.waitForChanges();
40
- // Select October 2021
41
- // The year will automatically switch to 2021 when selecting 10
42
- await monthColumn.locator('.picker-item[data-value="10"]').click();
43
- await ionChange.next();
44
- // Close month/year picker
45
- await monthYearButton.click();
46
34
  await page.waitForChanges();
47
35
  // Check that correct day is highlighted
48
36
  await expect(activeDayButton).toHaveAttribute('data-day', '5');
49
37
  await expect(activeDayButton).toHaveAttribute('data-month', '10');
50
38
  await expect(activeDayButton).toHaveAttribute('data-year', '2021');
51
39
  });
40
+ test('should scroll to new month when value is initially set and then updated', async ({ page }) => {
41
+ await page.goto('/src/components/datetime/test/set-value', config);
42
+ await page.waitForSelector('.datetime-ready');
43
+ const datetime = page.locator('ion-datetime');
44
+ await datetime.evaluate((el) => (el.value = '2021-05-25T12:40:00.000Z'));
45
+ await page.waitForChanges();
46
+ const calendarHeader = datetime.locator('.calendar-month-year');
47
+ await expect(calendarHeader).toHaveText(/May 2021/);
48
+ });
52
49
  });
53
50
  });