@ionic/core 9.0.9-nightly.20261020 → 9.0.9

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 (33) hide show
  1. package/components/ion-action-sheet.js +1 -1
  2. package/components/ion-alert.js +1 -1
  3. package/components/ion-backdrop.js +1 -1
  4. package/components/ion-datetime.js +1 -1
  5. package/components/ion-loading.js +1 -1
  6. package/components/ion-menu.js +1 -1
  7. package/components/ion-modal.js +1 -1
  8. package/components/ion-popover.js +1 -1
  9. package/components/ion-select.js +1 -1
  10. package/components/{p-DhfJAkyd.js → p-CKTjkpAq.js} +1 -1
  11. package/components/{p-DnGlmM4u.js → p-CONpe3oX.js} +1 -1
  12. package/components/p-CVrOcN5N.js +4 -0
  13. package/components/{p-DMPsc_0r.js → p-CW84OFQa.js} +1 -1
  14. package/components/{p-CQKWzUsE.js → p-CYctYy9B.js} +1 -1
  15. package/dist/cjs/ion-backdrop.cjs.entry.js +5 -1
  16. package/dist/cjs/ion-datetime.cjs.entry.js +55 -9
  17. package/dist/collection/components/backdrop/backdrop.js +6 -2
  18. package/dist/collection/components/datetime/datetime.js +56 -10
  19. package/dist/docs.json +3 -3
  20. package/dist/esm/ion-backdrop.entry.js +5 -1
  21. package/dist/esm/ion-datetime.entry.js +55 -9
  22. package/dist/html.html-data.json +1 -1
  23. package/dist/ionic/ionic.esm.js +1 -1
  24. package/dist/ionic/{p-230f510c.entry.js → p-76c189e7.entry.js} +1 -1
  25. package/dist/ionic/p-94f1f7df.entry.js +4 -0
  26. package/dist/types/components/backdrop/backdrop.d.ts +4 -0
  27. package/dist/types/components/datetime/datetime.d.ts +20 -3
  28. package/dist/types/components.d.ts +3 -3
  29. package/hydrate/index.js +60 -10
  30. package/hydrate/index.mjs +60 -10
  31. package/package.json +1 -1
  32. package/components/p-CQnEg-p2.js +0 -4
  33. package/dist/ionic/p-d9a3dfd1.entry.js +0 -4
@@ -767,6 +767,13 @@ const Datetime = class {
767
767
  this.ionStyle = index.createEvent(this, "ionStyle", 7);
768
768
  this.ionRender = index.createEvent(this, "ionRender", 7);
769
769
  this.inputId = `ion-dt-${datetimeIds++}`;
770
+ /**
771
+ * Set when the user selects a value, cleared when it's confirmed
772
+ * or `processValue` runs (`reset()`, or `value` being set to a
773
+ * non-empty value). The overlay present listener only discards a
774
+ * selection while this is set.
775
+ */
776
+ this.hasUnconfirmedSelection = false;
770
777
  this.prevPresentation = null;
771
778
  this.showMonthAndYear = false;
772
779
  this.activeParts = [];
@@ -930,6 +937,7 @@ const Datetime = class {
930
937
  if (popoverOrModal) {
931
938
  popoverOrModal.dismiss(undefined, role);
932
939
  }
940
+ return popoverOrModal !== null;
933
941
  };
934
942
  this.setWorkingParts = (parts) => {
935
943
  this.workingParts = {
@@ -944,6 +952,7 @@ const Datetime = class {
944
952
  if (this.readonly) {
945
953
  return;
946
954
  }
955
+ this.hasUnconfirmedSelection = true;
947
956
  const { multiple, minParts, maxParts, activeParts } = this;
948
957
  /**
949
958
  * When setting the active parts, it is possible
@@ -1364,7 +1373,8 @@ const Datetime = class {
1364
1373
  this.loadTimeout = undefined;
1365
1374
  }
1366
1375
  };
1367
- this.processValue = (value) => {
1376
+ this.processValue = (value, animated = true) => {
1377
+ this.hasUnconfirmedSelection = false;
1368
1378
  const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
1369
1379
  const valueToProcess = hasValue ? format.parseDate(value) : this.defaultParts;
1370
1380
  const { minParts, maxParts, workingParts, el } = this;
@@ -1427,13 +1437,14 @@ const Datetime = class {
1427
1437
  const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
1428
1438
  const bodyIsVisible = el.classList.contains('datetime-ready');
1429
1439
  const { isGridStyle, showMonthAndYear } = this;
1430
- if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
1440
+ if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear && animated) {
1431
1441
  /**
1432
1442
  * Only animate if:
1433
1443
  * 1. We're using grid style (wheel style pickers should just jump to new value)
1434
1444
  * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
1435
1445
  * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
1436
1446
  * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
1447
+ * 5. The caller did not opt out (e.g. the parent overlay is still hidden)
1437
1448
  */
1438
1449
  this.animateToDate(targetValue);
1439
1450
  }
@@ -1604,6 +1615,7 @@ const Datetime = class {
1604
1615
  this.setValue(format.convertDataToISO(activeParts));
1605
1616
  }
1606
1617
  }
1618
+ this.hasUnconfirmedSelection = false;
1607
1619
  if (closeOverlay) {
1608
1620
  this.closeParentOverlay(CONFIRM_ROLE);
1609
1621
  }
@@ -1619,17 +1631,22 @@ const Datetime = class {
1619
1631
  this.processValue(startDate);
1620
1632
  }
1621
1633
  /**
1622
- * Emits the ionCancel event and
1623
- * optionally closes the popover
1624
- * or modal that the datetime was
1634
+ * Emits the ionCancel event, discards
1635
+ * any unconfirmed selection, and
1636
+ * optionally closes the popover or
1637
+ * modal that the datetime was
1625
1638
  * presented in.
1626
1639
  *
1627
1640
  * @param closeOverlay If `true`, closes the parent overlay. Defaults to `false`.
1628
1641
  */
1629
1642
  async cancel(closeOverlay = false) {
1630
1643
  this.ionCancel.emit();
1631
- if (closeOverlay) {
1632
- this.closeParentOverlay(CANCEL_ROLE);
1644
+ /**
1645
+ * When a parent overlay closes, the present listener discards
1646
+ * the selection on the next open. Otherwise discard it now.
1647
+ */
1648
+ if (!closeOverlay || !this.closeParentOverlay(CANCEL_ROLE)) {
1649
+ this.processValue(this.value);
1633
1650
  }
1634
1651
  }
1635
1652
  /**
@@ -1651,6 +1668,7 @@ const Datetime = class {
1651
1668
  this.loadTimeout = setTimeout(() => {
1652
1669
  this.ensureReadyIfVisible();
1653
1670
  }, 100);
1671
+ this.initializeOverlayPresentListener();
1654
1672
  }
1655
1673
  disconnectedCallback() {
1656
1674
  if (this.clearFocusVisible) {
@@ -1658,6 +1676,34 @@ const Datetime = class {
1658
1676
  this.clearFocusVisible = undefined;
1659
1677
  }
1660
1678
  this.loadTimeoutCleanup();
1679
+ if (this.destroyOverlayPresentListener) {
1680
+ this.destroyOverlayPresentListener();
1681
+ this.destroyOverlayPresentListener = undefined;
1682
+ }
1683
+ }
1684
+ /**
1685
+ * A datetime in a modal or popover stays mounted between
1686
+ * presentations, so an unconfirmed selection would still show when
1687
+ * it reopens. Discarding on present rather than dismiss lets app
1688
+ * code that runs on dismiss still confirm or reset the selection,
1689
+ * whatever order its listeners were added in.
1690
+ */
1691
+ initializeOverlayPresentListener() {
1692
+ const overlay = this.el.closest('ion-modal, ion-popover');
1693
+ if (!overlay) {
1694
+ return;
1695
+ }
1696
+ const eventName = overlay.tagName === 'ION-MODAL' ? 'ionModalWillPresent' : 'ionPopoverWillPresent';
1697
+ const onPresent = (ev) => {
1698
+ // Ignore nested overlays and skip when there's nothing to discard.
1699
+ if (ev.target !== overlay || !this.hasUnconfirmedSelection) {
1700
+ return;
1701
+ }
1702
+ // The overlay is still hidden, so skip the month animation.
1703
+ this.processValue(this.value, false);
1704
+ };
1705
+ overlay.addEventListener(eventName, onPresent);
1706
+ this.destroyOverlayPresentListener = () => overlay.removeEventListener(eventName, onPresent);
1661
1707
  }
1662
1708
  initializeListeners() {
1663
1709
  this.initializeCalendarListener();
@@ -2540,7 +2586,7 @@ const Datetime = class {
2540
2586
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
2541
2587
  const hasWheelVariant = hasDatePresentation && preferWheel;
2542
2588
  helpers.renderHiddenInput(true, el, name, format.formatValue(value), disabled);
2543
- return (index.h(index.Host, { key: 'a9128b5c55464e30a269daf9d725bf430c8c655c', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: {
2589
+ return (index.h(index.Host, { key: 'e9b99bb5610fc7fe879200a56dcc865c6e2f1404', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: {
2544
2590
  ...theme.createColorClasses(color, {
2545
2591
  [mode]: true,
2546
2592
  ['datetime-readonly']: readonly,
@@ -2552,7 +2598,7 @@ const Datetime = class {
2552
2598
  [`datetime-prefer-wheel`]: hasWheelVariant,
2553
2599
  [`datetime-grid`]: isGridStyle,
2554
2600
  }),
2555
- } }, index.h("div", { key: 'b49f3a98db2b7cf535d7ea894d518f75f8a1fefb', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
2601
+ } }, index.h("div", { key: 'a5e61be2989468dc24ec8fb394116d9339b7824c', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
2556
2602
  }
2557
2603
  get el() { return index.getElement(this); }
2558
2604
  static get watchers() { return {
@@ -7,6 +7,10 @@ export class Backdrop {
7
7
  constructor() {
8
8
  /**
9
9
  * If `true`, the backdrop will be visible.
10
+ * This property controls whether or not the backdrop's
11
+ * default background is shown. It does not control whether
12
+ * or not the backdrop blocks interaction, and it does not
13
+ * override a custom background set on `ion-backdrop`.
10
14
  */
11
15
  this.visible = true;
12
16
  /**
@@ -32,7 +36,7 @@ export class Backdrop {
32
36
  }
33
37
  render() {
34
38
  const mode = getIonMode(this);
35
- return (h(Host, { key: '7abaf2c310aa399607451b14063265e8a5846938', "aria-hidden": "true", class: {
39
+ return (h(Host, { key: '14141148f4a22371b8ad7cb9dcdc32398ec109aa', "aria-hidden": "true", class: {
36
40
  [mode]: true,
37
41
  'backdrop-hide': !this.visible,
38
42
  'backdrop-no-tappable': !this.tappable,
@@ -66,7 +70,7 @@ export class Backdrop {
66
70
  "optional": false,
67
71
  "docs": {
68
72
  "tags": [],
69
- "text": "If `true`, the backdrop will be visible."
73
+ "text": "If `true`, the backdrop will be visible.\nThis property controls whether or not the backdrop's\ndefault background is shown. It does not control whether\nor not the backdrop blocks interaction, and it does not\noverride a custom background set on `ion-backdrop`."
70
74
  },
71
75
  "getter": false,
72
76
  "setter": false,
@@ -56,6 +56,13 @@ import { checkForPresentationFormatMismatch, warnIfTimeZoneProvided } from "./ut
56
56
  export class Datetime {
57
57
  constructor() {
58
58
  this.inputId = `ion-dt-${datetimeIds++}`;
59
+ /**
60
+ * Set when the user selects a value, cleared when it's confirmed
61
+ * or `processValue` runs (`reset()`, or `value` being set to a
62
+ * non-empty value). The overlay present listener only discards a
63
+ * selection while this is set.
64
+ */
65
+ this.hasUnconfirmedSelection = false;
59
66
  this.prevPresentation = null;
60
67
  this.showMonthAndYear = false;
61
68
  this.activeParts = [];
@@ -219,6 +226,7 @@ export class Datetime {
219
226
  if (popoverOrModal) {
220
227
  popoverOrModal.dismiss(undefined, role);
221
228
  }
229
+ return popoverOrModal !== null;
222
230
  };
223
231
  this.setWorkingParts = (parts) => {
224
232
  this.workingParts = {
@@ -233,6 +241,7 @@ export class Datetime {
233
241
  if (this.readonly) {
234
242
  return;
235
243
  }
244
+ this.hasUnconfirmedSelection = true;
236
245
  const { multiple, minParts, maxParts, activeParts } = this;
237
246
  /**
238
247
  * When setting the active parts, it is possible
@@ -653,7 +662,8 @@ export class Datetime {
653
662
  this.loadTimeout = undefined;
654
663
  }
655
664
  };
656
- this.processValue = (value) => {
665
+ this.processValue = (value, animated = true) => {
666
+ this.hasUnconfirmedSelection = false;
657
667
  const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
658
668
  const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
659
669
  const { minParts, maxParts, workingParts, el } = this;
@@ -716,13 +726,14 @@ export class Datetime {
716
726
  const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
717
727
  const bodyIsVisible = el.classList.contains('datetime-ready');
718
728
  const { isGridStyle, showMonthAndYear } = this;
719
- if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
729
+ if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear && animated) {
720
730
  /**
721
731
  * Only animate if:
722
732
  * 1. We're using grid style (wheel style pickers should just jump to new value)
723
733
  * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
724
734
  * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
725
735
  * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
736
+ * 5. The caller did not opt out (e.g. the parent overlay is still hidden)
726
737
  */
727
738
  this.animateToDate(targetValue);
728
739
  }
@@ -893,6 +904,7 @@ export class Datetime {
893
904
  this.setValue(convertDataToISO(activeParts));
894
905
  }
895
906
  }
907
+ this.hasUnconfirmedSelection = false;
896
908
  if (closeOverlay) {
897
909
  this.closeParentOverlay(CONFIRM_ROLE);
898
910
  }
@@ -908,17 +920,22 @@ export class Datetime {
908
920
  this.processValue(startDate);
909
921
  }
910
922
  /**
911
- * Emits the ionCancel event and
912
- * optionally closes the popover
913
- * or modal that the datetime was
923
+ * Emits the ionCancel event, discards
924
+ * any unconfirmed selection, and
925
+ * optionally closes the popover or
926
+ * modal that the datetime was
914
927
  * presented in.
915
928
  *
916
929
  * @param closeOverlay If `true`, closes the parent overlay. Defaults to `false`.
917
930
  */
918
931
  async cancel(closeOverlay = false) {
919
932
  this.ionCancel.emit();
920
- if (closeOverlay) {
921
- this.closeParentOverlay(CANCEL_ROLE);
933
+ /**
934
+ * When a parent overlay closes, the present listener discards
935
+ * the selection on the next open. Otherwise discard it now.
936
+ */
937
+ if (!closeOverlay || !this.closeParentOverlay(CANCEL_ROLE)) {
938
+ this.processValue(this.value);
922
939
  }
923
940
  }
924
941
  /**
@@ -940,6 +957,7 @@ export class Datetime {
940
957
  this.loadTimeout = setTimeout(() => {
941
958
  this.ensureReadyIfVisible();
942
959
  }, 100);
960
+ this.initializeOverlayPresentListener();
943
961
  }
944
962
  disconnectedCallback() {
945
963
  if (this.clearFocusVisible) {
@@ -947,6 +965,34 @@ export class Datetime {
947
965
  this.clearFocusVisible = undefined;
948
966
  }
949
967
  this.loadTimeoutCleanup();
968
+ if (this.destroyOverlayPresentListener) {
969
+ this.destroyOverlayPresentListener();
970
+ this.destroyOverlayPresentListener = undefined;
971
+ }
972
+ }
973
+ /**
974
+ * A datetime in a modal or popover stays mounted between
975
+ * presentations, so an unconfirmed selection would still show when
976
+ * it reopens. Discarding on present rather than dismiss lets app
977
+ * code that runs on dismiss still confirm or reset the selection,
978
+ * whatever order its listeners were added in.
979
+ */
980
+ initializeOverlayPresentListener() {
981
+ const overlay = this.el.closest('ion-modal, ion-popover');
982
+ if (!overlay) {
983
+ return;
984
+ }
985
+ const eventName = overlay.tagName === 'ION-MODAL' ? 'ionModalWillPresent' : 'ionPopoverWillPresent';
986
+ const onPresent = (ev) => {
987
+ // Ignore nested overlays and skip when there's nothing to discard.
988
+ if (ev.target !== overlay || !this.hasUnconfirmedSelection) {
989
+ return;
990
+ }
991
+ // The overlay is still hidden, so skip the month animation.
992
+ this.processValue(this.value, false);
993
+ };
994
+ overlay.addEventListener(eventName, onPresent);
995
+ this.destroyOverlayPresentListener = () => overlay.removeEventListener(eventName, onPresent);
950
996
  }
951
997
  initializeListeners() {
952
998
  this.initializeCalendarListener();
@@ -1829,7 +1875,7 @@ export class Datetime {
1829
1875
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1830
1876
  const hasWheelVariant = hasDatePresentation && preferWheel;
1831
1877
  renderHiddenInput(true, el, name, formatValue(value), disabled);
1832
- return (h(Host, { key: 'a9128b5c55464e30a269daf9d725bf430c8c655c', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: {
1878
+ return (h(Host, { key: 'e9b99bb5610fc7fe879200a56dcc865c6e2f1404', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: {
1833
1879
  ...createColorClasses(color, {
1834
1880
  [mode]: true,
1835
1881
  ['datetime-readonly']: readonly,
@@ -1841,7 +1887,7 @@ export class Datetime {
1841
1887
  [`datetime-prefer-wheel`]: hasWheelVariant,
1842
1888
  [`datetime-grid`]: isGridStyle,
1843
1889
  }),
1844
- } }, h("div", { key: 'b49f3a98db2b7cf535d7ea894d518f75f8a1fefb', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1890
+ } }, h("div", { key: 'a5e61be2989468dc24ec8fb394116d9339b7824c', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1845
1891
  }
1846
1892
  static get is() { return "ion-datetime"; }
1847
1893
  static get encapsulation() { return "shadow"; }
@@ -2721,7 +2767,7 @@ export class Datetime {
2721
2767
  "return": "Promise<void>"
2722
2768
  },
2723
2769
  "docs": {
2724
- "text": "Emits the ionCancel event and\noptionally closes the popover\nor modal that the datetime was\npresented in.",
2770
+ "text": "Emits the ionCancel event, discards\nany unconfirmed selection, and\noptionally closes the popover or\nmodal that the datetime was\npresented in.",
2725
2771
  "tags": [{
2726
2772
  "name": "param",
2727
2773
  "text": "closeOverlay If `true`, closes the parent overlay. Defaults to `false`."
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-10-20T06:17:13",
2
+ "timestamp": "2026-10-21T18:13:17",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.45.2",
@@ -3315,7 +3315,7 @@
3315
3315
  "mutable": false,
3316
3316
  "attr": "visible",
3317
3317
  "reflectToAttr": false,
3318
- "docs": "If `true`, the backdrop will be visible.",
3318
+ "docs": "If `true`, the backdrop will be visible.\nThis property controls whether or not the backdrop's\ndefault background is shown. It does not control whether\nor not the backdrop blocks interaction, and it does not\noverride a custom background set on `ion-backdrop`.",
3319
3319
  "docsTags": [
3320
3320
  {
3321
3321
  "name": "default",
@@ -9696,7 +9696,7 @@
9696
9696
  "docs": "If `true`, closes the parent overlay. Defaults to `false`."
9697
9697
  }
9698
9698
  ],
9699
- "docs": "Emits the ionCancel event and\noptionally closes the popover\nor modal that the datetime was\npresented in.",
9699
+ "docs": "Emits the ionCancel event, discards\nany unconfirmed selection, and\noptionally closes the popover or\nmodal that the datetime was\npresented in.",
9700
9700
  "docsTags": [
9701
9701
  {
9702
9702
  "name": "param",
@@ -14,6 +14,10 @@ const Backdrop = class {
14
14
  this.ionBackdropTap = createEvent(this, "ionBackdropTap", 7);
15
15
  /**
16
16
  * If `true`, the backdrop will be visible.
17
+ * This property controls whether or not the backdrop's
18
+ * default background is shown. It does not control whether
19
+ * or not the backdrop blocks interaction, and it does not
20
+ * override a custom background set on `ion-backdrop`.
17
21
  */
18
22
  this.visible = true;
19
23
  /**
@@ -39,7 +43,7 @@ const Backdrop = class {
39
43
  }
40
44
  render() {
41
45
  const mode = getIonMode(this);
42
- return (h(Host, { key: '7abaf2c310aa399607451b14063265e8a5846938', "aria-hidden": "true", class: {
46
+ return (h(Host, { key: '14141148f4a22371b8ad7cb9dcdc32398ec109aa', "aria-hidden": "true", class: {
43
47
  [mode]: true,
44
48
  'backdrop-hide': !this.visible,
45
49
  'backdrop-no-tappable': !this.tappable,
@@ -765,6 +765,13 @@ const Datetime = class {
765
765
  this.ionStyle = createEvent(this, "ionStyle", 7);
766
766
  this.ionRender = createEvent(this, "ionRender", 7);
767
767
  this.inputId = `ion-dt-${datetimeIds++}`;
768
+ /**
769
+ * Set when the user selects a value, cleared when it's confirmed
770
+ * or `processValue` runs (`reset()`, or `value` being set to a
771
+ * non-empty value). The overlay present listener only discards a
772
+ * selection while this is set.
773
+ */
774
+ this.hasUnconfirmedSelection = false;
768
775
  this.prevPresentation = null;
769
776
  this.showMonthAndYear = false;
770
777
  this.activeParts = [];
@@ -928,6 +935,7 @@ const Datetime = class {
928
935
  if (popoverOrModal) {
929
936
  popoverOrModal.dismiss(undefined, role);
930
937
  }
938
+ return popoverOrModal !== null;
931
939
  };
932
940
  this.setWorkingParts = (parts) => {
933
941
  this.workingParts = {
@@ -942,6 +950,7 @@ const Datetime = class {
942
950
  if (this.readonly) {
943
951
  return;
944
952
  }
953
+ this.hasUnconfirmedSelection = true;
945
954
  const { multiple, minParts, maxParts, activeParts } = this;
946
955
  /**
947
956
  * When setting the active parts, it is possible
@@ -1362,7 +1371,8 @@ const Datetime = class {
1362
1371
  this.loadTimeout = undefined;
1363
1372
  }
1364
1373
  };
1365
- this.processValue = (value) => {
1374
+ this.processValue = (value, animated = true) => {
1375
+ this.hasUnconfirmedSelection = false;
1366
1376
  const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
1367
1377
  const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
1368
1378
  const { minParts, maxParts, workingParts, el } = this;
@@ -1425,13 +1435,14 @@ const Datetime = class {
1425
1435
  const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
1426
1436
  const bodyIsVisible = el.classList.contains('datetime-ready');
1427
1437
  const { isGridStyle, showMonthAndYear } = this;
1428
- if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
1438
+ if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear && animated) {
1429
1439
  /**
1430
1440
  * Only animate if:
1431
1441
  * 1. We're using grid style (wheel style pickers should just jump to new value)
1432
1442
  * 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
1433
1443
  * 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
1434
1444
  * 4. The month/year picker is not open (since you wouldn't see the animation anyway)
1445
+ * 5. The caller did not opt out (e.g. the parent overlay is still hidden)
1435
1446
  */
1436
1447
  this.animateToDate(targetValue);
1437
1448
  }
@@ -1602,6 +1613,7 @@ const Datetime = class {
1602
1613
  this.setValue(convertDataToISO(activeParts));
1603
1614
  }
1604
1615
  }
1616
+ this.hasUnconfirmedSelection = false;
1605
1617
  if (closeOverlay) {
1606
1618
  this.closeParentOverlay(CONFIRM_ROLE);
1607
1619
  }
@@ -1617,17 +1629,22 @@ const Datetime = class {
1617
1629
  this.processValue(startDate);
1618
1630
  }
1619
1631
  /**
1620
- * Emits the ionCancel event and
1621
- * optionally closes the popover
1622
- * or modal that the datetime was
1632
+ * Emits the ionCancel event, discards
1633
+ * any unconfirmed selection, and
1634
+ * optionally closes the popover or
1635
+ * modal that the datetime was
1623
1636
  * presented in.
1624
1637
  *
1625
1638
  * @param closeOverlay If `true`, closes the parent overlay. Defaults to `false`.
1626
1639
  */
1627
1640
  async cancel(closeOverlay = false) {
1628
1641
  this.ionCancel.emit();
1629
- if (closeOverlay) {
1630
- this.closeParentOverlay(CANCEL_ROLE);
1642
+ /**
1643
+ * When a parent overlay closes, the present listener discards
1644
+ * the selection on the next open. Otherwise discard it now.
1645
+ */
1646
+ if (!closeOverlay || !this.closeParentOverlay(CANCEL_ROLE)) {
1647
+ this.processValue(this.value);
1631
1648
  }
1632
1649
  }
1633
1650
  /**
@@ -1649,6 +1666,7 @@ const Datetime = class {
1649
1666
  this.loadTimeout = setTimeout(() => {
1650
1667
  this.ensureReadyIfVisible();
1651
1668
  }, 100);
1669
+ this.initializeOverlayPresentListener();
1652
1670
  }
1653
1671
  disconnectedCallback() {
1654
1672
  if (this.clearFocusVisible) {
@@ -1656,6 +1674,34 @@ const Datetime = class {
1656
1674
  this.clearFocusVisible = undefined;
1657
1675
  }
1658
1676
  this.loadTimeoutCleanup();
1677
+ if (this.destroyOverlayPresentListener) {
1678
+ this.destroyOverlayPresentListener();
1679
+ this.destroyOverlayPresentListener = undefined;
1680
+ }
1681
+ }
1682
+ /**
1683
+ * A datetime in a modal or popover stays mounted between
1684
+ * presentations, so an unconfirmed selection would still show when
1685
+ * it reopens. Discarding on present rather than dismiss lets app
1686
+ * code that runs on dismiss still confirm or reset the selection,
1687
+ * whatever order its listeners were added in.
1688
+ */
1689
+ initializeOverlayPresentListener() {
1690
+ const overlay = this.el.closest('ion-modal, ion-popover');
1691
+ if (!overlay) {
1692
+ return;
1693
+ }
1694
+ const eventName = overlay.tagName === 'ION-MODAL' ? 'ionModalWillPresent' : 'ionPopoverWillPresent';
1695
+ const onPresent = (ev) => {
1696
+ // Ignore nested overlays and skip when there's nothing to discard.
1697
+ if (ev.target !== overlay || !this.hasUnconfirmedSelection) {
1698
+ return;
1699
+ }
1700
+ // The overlay is still hidden, so skip the month animation.
1701
+ this.processValue(this.value, false);
1702
+ };
1703
+ overlay.addEventListener(eventName, onPresent);
1704
+ this.destroyOverlayPresentListener = () => overlay.removeEventListener(eventName, onPresent);
1659
1705
  }
1660
1706
  initializeListeners() {
1661
1707
  this.initializeCalendarListener();
@@ -2538,7 +2584,7 @@ const Datetime = class {
2538
2584
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
2539
2585
  const hasWheelVariant = hasDatePresentation && preferWheel;
2540
2586
  renderHiddenInput(true, el, name, formatValue(value), disabled);
2541
- return (h(Host, { key: 'a9128b5c55464e30a269daf9d725bf430c8c655c', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: {
2587
+ return (h(Host, { key: 'e9b99bb5610fc7fe879200a56dcc865c6e2f1404', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: {
2542
2588
  ...createColorClasses(color, {
2543
2589
  [mode]: true,
2544
2590
  ['datetime-readonly']: readonly,
@@ -2550,7 +2596,7 @@ const Datetime = class {
2550
2596
  [`datetime-prefer-wheel`]: hasWheelVariant,
2551
2597
  [`datetime-grid`]: isGridStyle,
2552
2598
  }),
2553
- } }, h("div", { key: 'b49f3a98db2b7cf535d7ea894d518f75f8a1fefb', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
2599
+ } }, h("div", { key: 'a5e61be2989468dc24ec8fb394116d9339b7824c', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
2554
2600
  }
2555
2601
  get el() { return getElement(this); }
2556
2602
  static get watchers() { return {
@@ -381,7 +381,7 @@
381
381
  },
382
382
  {
383
383
  "name": "visible",
384
- "description": "If `true`, the backdrop will be visible."
384
+ "description": "If `true`, the backdrop will be visible.\nThis property controls whether or not the backdrop's\ndefault background is shown. It does not control whether\nor not the backdrop blocks interaction, and it does not\noverride a custom background set on `ion-backdrop`."
385
385
  }
386
386
  ],
387
387
  "references": [