voyager-ionic-core 9.0.7 → 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 (114) hide show
  1. package/components/index.js +1 -1
  2. package/components/ion-action-sheet.js +1 -1
  3. package/components/ion-alert.js +1 -1
  4. package/components/ion-backdrop.js +1 -1
  5. package/components/ion-datetime.js +1 -1
  6. package/components/ion-input.js +1 -1
  7. package/components/ion-loading.js +1 -1
  8. package/components/ion-menu.js +1 -1
  9. package/components/ion-modal.js +1 -1
  10. package/components/ion-nav.js +1 -1
  11. package/components/ion-popover.js +1 -1
  12. package/components/ion-router-outlet.js +1 -1
  13. package/components/ion-select.js +1 -1
  14. package/components/ion-textarea.js +1 -1
  15. package/components/p-Bd8hg9FK.js +4 -0
  16. package/components/{p-DhfJAkyd.js → p-CKTjkpAq.js} +1 -1
  17. package/components/{p-DnGlmM4u.js → p-CONpe3oX.js} +1 -1
  18. package/components/{p-DMPsc_0r.js → p-CW84OFQa.js} +1 -1
  19. package/components/{p-D64Tyi-z.js → p-CYctYy9B.js} +1 -1
  20. package/components/{p-Dvyv9SMk.js → p-D0nwlsl_.js} +1 -1
  21. package/components/p-DfDIY2oP.js +4 -0
  22. package/components/p-SHHIB5F0.js +4 -0
  23. package/components/p-zQi7R3tS.js +4 -0
  24. package/css/core.css.map +1 -1
  25. package/css/ionic.bundle.css.map +1 -1
  26. package/dist/cjs/{index-DUiuw_fH.js → index-DCfr5hz-.js} +14 -2
  27. package/dist/cjs/index.cjs.js +3 -3
  28. package/dist/cjs/ion-app_8.cjs.entry.js +31 -3
  29. package/dist/cjs/ion-backdrop.cjs.entry.js +5 -1
  30. package/dist/cjs/ion-datetime.cjs.entry.js +55 -9
  31. package/dist/cjs/ion-input.cjs.entry.js +13 -13
  32. package/dist/cjs/ion-modal.cjs.entry.js +31 -81
  33. package/dist/cjs/ion-nav_2.cjs.entry.js +24 -2
  34. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  35. package/dist/cjs/ion-select_3.cjs.entry.js +12 -12
  36. package/dist/cjs/ion-textarea.cjs.entry.js +12 -12
  37. package/dist/cjs/{ios.transition-D85_rHf5.js → ios.transition-Ca6XODo9.js} +1 -1
  38. package/dist/cjs/{md.transition-Ce9X1WXr.js → md.transition-D8SfNYEp.js} +1 -1
  39. package/dist/cjs/{slot-mutation-controller-ClDxh3EK.js → slot-mutation-controller-B2OfJEBH.js} +67 -33
  40. package/dist/collection/components/backdrop/backdrop.js +6 -2
  41. package/dist/collection/components/datetime/datetime.js +56 -10
  42. package/dist/collection/components/input/input.ios.css +7 -3
  43. package/dist/collection/components/input/input.js +11 -11
  44. package/dist/collection/components/input/input.md.css +28 -15
  45. package/dist/collection/components/modal/gestures/sheet.js +30 -80
  46. package/dist/collection/components/nav/nav.js +24 -2
  47. package/dist/collection/components/router-outlet/router-outlet.js +31 -3
  48. package/dist/collection/components/select/select.ios.css +7 -3
  49. package/dist/collection/components/select/select.js +10 -10
  50. package/dist/collection/components/select/select.md.css +28 -15
  51. package/dist/collection/components/textarea/textarea.ios.css +7 -3
  52. package/dist/collection/components/textarea/textarea.js +10 -10
  53. package/dist/collection/components/textarea/textarea.md.css +28 -17
  54. package/dist/collection/utils/forms/index.js +1 -1
  55. package/dist/collection/utils/forms/{start-container-controller.js → start-end-container-controller.js} +66 -32
  56. package/dist/collection/utils/transition/index.js +11 -0
  57. package/dist/docs.json +3 -3
  58. package/dist/esm/{index-BBASprVu.js → index-z9vdZN7I.js} +14 -3
  59. package/dist/esm/index.js +3 -3
  60. package/dist/esm/ion-app_8.entry.js +31 -3
  61. package/dist/esm/ion-backdrop.entry.js +5 -1
  62. package/dist/esm/ion-datetime.entry.js +55 -9
  63. package/dist/esm/ion-input.entry.js +13 -13
  64. package/dist/esm/ion-modal.entry.js +31 -81
  65. package/dist/esm/ion-nav_2.entry.js +24 -2
  66. package/dist/esm/ion-popover.entry.js +1 -1
  67. package/dist/esm/ion-select_3.entry.js +12 -12
  68. package/dist/esm/ion-textarea.entry.js +12 -12
  69. package/dist/esm/{ios.transition-B8netzsb.js → ios.transition-BkUGiDUz.js} +1 -1
  70. package/dist/esm/{md.transition-DNCSSiXk.js → md.transition-B6hlrWjR.js} +1 -1
  71. package/dist/esm/{slot-mutation-controller-hAS2uJ7c.js → slot-mutation-controller-Btpr9-9O.js} +67 -33
  72. package/dist/html.html-data.json +1 -1
  73. package/dist/ionic/index.esm.js +1 -1
  74. package/dist/ionic/ionic.esm.js +1 -1
  75. package/dist/ionic/p-293cb1d5.entry.js +4 -0
  76. package/dist/ionic/p-61321cfa.entry.js +4 -0
  77. package/dist/ionic/p-657239f5.entry.js +4 -0
  78. package/dist/ionic/p-67f004eb.entry.js +4 -0
  79. package/dist/ionic/{p-230f510c.entry.js → p-76c189e7.entry.js} +1 -1
  80. package/dist/ionic/p-94f1f7df.entry.js +4 -0
  81. package/dist/ionic/{p-Bppj2PE0.js → p-BZTgGzGK.js} +1 -1
  82. package/dist/ionic/p-BpED_ep_.js +4 -0
  83. package/dist/ionic/{p-Bnc1L7Rk.js → p-CgcGX08t.js} +1 -1
  84. package/dist/ionic/p-Dk4Mdaru.js +4 -0
  85. package/dist/ionic/{p-4ed3c460.entry.js → p-a7d368b5.entry.js} +1 -1
  86. package/dist/ionic/p-e8e69c21.entry.js +4 -0
  87. package/dist/ionic/{p-af56702e.entry.js → p-f2116a54.entry.js} +1 -1
  88. package/dist/types/components/backdrop/backdrop.d.ts +4 -0
  89. package/dist/types/components/datetime/datetime.d.ts +20 -3
  90. package/dist/types/components/input/input.d.ts +2 -1
  91. package/dist/types/components/nav/nav.d.ts +1 -0
  92. package/dist/types/components/router-outlet/router-outlet.d.ts +1 -0
  93. package/dist/types/components/select/select.d.ts +2 -1
  94. package/dist/types/components/textarea/textarea.d.ts +2 -1
  95. package/dist/types/components.d.ts +3 -3
  96. package/dist/types/utils/forms/index.d.ts +1 -1
  97. package/dist/types/utils/forms/start-end-container-controller.d.ts +44 -0
  98. package/dist/types/utils/transition/index.d.ts +6 -0
  99. package/hydrate/index.js +254 -159
  100. package/hydrate/index.mjs +254 -159
  101. package/package.json +4 -4
  102. package/components/p-C7r5Gja6.js +0 -4
  103. package/components/p-CtiO6_sC.js +0 -4
  104. package/components/p-TLtgwTFF.js +0 -4
  105. package/components/p-Uk13YgEc.js +0 -4
  106. package/dist/ionic/p-27d96a9b.entry.js +0 -4
  107. package/dist/ionic/p-4d785062.entry.js +0 -4
  108. package/dist/ionic/p-6317d620.entry.js +0 -4
  109. package/dist/ionic/p-8b79061e.entry.js +0 -4
  110. package/dist/ionic/p-B8IuTu_r.js +0 -4
  111. package/dist/ionic/p-DQXtveHv.js +0 -4
  112. package/dist/ionic/p-c81ec0e8.entry.js +0 -4
  113. package/dist/ionic/p-d9a3dfd1.entry.js +0 -4
  114. package/dist/types/utils/forms/start-container-controller.d.ts +0 -34
@@ -167,10 +167,19 @@ const SKIP_LABEL_TRANSITION_CLASS = 'skip-label-transition';
167
167
  */
168
168
  const START_CONTAINER_ADJUSTMENT_VAR = '--internal-start-container-adjustment';
169
169
  /**
170
- * A utility to measure and update the start container width adjustment
171
- * for form components (input, textarea, select) with `fill="outline"`.
172
- * This adjustment compensates for the start container width when
173
- * positioning floating/stacked labels within the outline border.
170
+ * Set on the host as an inline style. The component stylesheets declare
171
+ * this property and read it with `var()`, so it must stay in sync with
172
+ * the `--internal-start-end-container-width` references in those files.
173
+ * Renaming it here requires renaming it there too.
174
+ */
175
+ const START_END_CONTAINER_WIDTH_VAR = '--internal-start-end-container-width';
176
+ /**
177
+ * A utility to measure the start and end containers of form components
178
+ * (input, textarea, select) with `fill="outline"` and write the results
179
+ * to the host. The start container width offsets floating/stacked labels
180
+ * so they line up with the outline border. The combined start and end
181
+ * container width lets the floating label size itself to the full width
182
+ * of the outline while still being positioned relative to the control.
174
183
  *
175
184
  * Measurement is deferred with raf() to avoid forcing layout during
176
185
  * component updates, and a ResizeObserver watches for dynamic width
@@ -179,29 +188,36 @@ const START_CONTAINER_ADJUSTMENT_VAR = '--internal-start-container-adjustment';
179
188
  * @internal
180
189
  * @param el - The host element (ion-input, ion-textarea, or ion-select).
181
190
  * @param getStartContainer - A callback that returns the start container element.
182
- * @param shouldApplyAdjustment - A callback that determines whether the adjustment should be applied.
191
+ * @param getEndContainer - A callback that returns the end container element.
192
+ * @param shouldMeasure - A callback that determines whether the containers should be measured.
183
193
  */
184
- const createStartContainerController = (el, getStartContainer, shouldApplyAdjustment) => {
194
+ const createStartEndContainerController = (el, getStartContainer, getEndContainer, shouldMeasure) => {
185
195
  let resizeObserver;
186
- let observedContainer;
196
+ let observedStartContainer;
197
+ let observedEndContainer;
187
198
  let measurementRaf;
188
199
  let transitionRaf;
189
200
  let appliedAdjustment;
190
- const calculateAdjustment = () => {
191
- const startSlot = getStartContainer();
192
- if (!startSlot || !shouldApplyAdjustment()) {
193
- return '';
201
+ let appliedStartEndWidth;
202
+ /*
203
+ * Round to one decimal place so fractional widths produce a stable
204
+ * label offset and width when a container is added, removed, or resized.
205
+ */
206
+ const roundWidth = (width) => Math.round(width * 10) / 10;
207
+ const measureContainers = () => {
208
+ const startContainer = getStartContainer();
209
+ if (!startContainer || !shouldMeasure()) {
210
+ return { adjustment: '', startEndWidth: '' };
194
211
  }
195
- /*
196
- * Round to one decimal place so fractional widths produce a stable
197
- * label offset when the start container is added, removed, or resized.
198
- */
199
- const startContainerWidth = startSlot.getBoundingClientRect().width;
200
- const roundedWidth = Math.round(startContainerWidth * 10) / 10;
212
+ const startContainerWidth = roundWidth(startContainer.getBoundingClientRect().width);
213
+ const endContainerWidth = roundWidth(getEndContainer()?.getBoundingClientRect().width ?? 0);
201
214
  const sign = dir.isRTL(el) ? '' : '-';
202
- return roundedWidth ? `${sign}${roundedWidth}px` : '0px';
215
+ return {
216
+ adjustment: startContainerWidth ? `${sign}${startContainerWidth}px` : '0px',
217
+ startEndWidth: `${roundWidth(startContainerWidth + endContainerWidth)}px`,
218
+ };
203
219
  };
204
- const scheduleAdjustmentUpdate = () => {
220
+ const scheduleMeasurement = () => {
205
221
  // Return if there is already a measurement queued to avoid a flicker
206
222
  if (measurementRaf !== undefined) {
207
223
  return;
@@ -212,7 +228,17 @@ const createStartContainerController = (el, getStartContainer, shouldApplyAdjust
212
228
  */
213
229
  measurementRaf = helpers.raf(() => {
214
230
  measurementRaf = undefined;
215
- const adjustment = calculateAdjustment();
231
+ const { adjustment, startEndWidth } = measureContainers();
232
+ /*
233
+ * The width only changes how much of the label fits, so it can
234
+ * animate. The transition is not suppressed here because the end
235
+ * container can change size while the label is floating, such as
236
+ * when async content loads, and that should not cut the float short.
237
+ */
238
+ if (startEndWidth !== appliedStartEndWidth) {
239
+ el.style.setProperty(START_END_CONTAINER_WIDTH_VAR, startEndWidth);
240
+ appliedStartEndWidth = startEndWidth;
241
+ }
216
242
  // Only suppress the label transition when the label has to move
217
243
  if (adjustment !== appliedAdjustment) {
218
244
  // Discard a removal queued by an earlier change
@@ -235,29 +261,36 @@ const createStartContainerController = (el, getStartContainer, shouldApplyAdjust
235
261
  if (resizeObserver) {
236
262
  resizeObserver.disconnect();
237
263
  resizeObserver = undefined;
238
- observedContainer = undefined;
264
+ observedStartContainer = undefined;
265
+ observedEndContainer = undefined;
239
266
  }
240
267
  };
241
268
  const setupResizeObserver = () => {
242
- const startSlot = getStartContainer();
243
- // Stop watching once the adjustment no longer applies
244
- if (!startSlot || !shouldApplyAdjustment()) {
269
+ const startContainer = getStartContainer();
270
+ const endContainer = getEndContainer();
271
+ // Stop watching once the measurements no longer apply
272
+ if (!startContainer || !shouldMeasure()) {
245
273
  disconnectResizeObserver();
246
274
  return;
247
275
  }
248
- // Return when prerendering or already watching this container
249
- if (typeof ResizeObserver !== 'function' || (resizeObserver && observedContainer === startSlot)) {
276
+ // Return when prerendering or already watching these containers
277
+ if (typeof ResizeObserver !== 'function' ||
278
+ (resizeObserver && observedStartContainer === startContainer && observedEndContainer === endContainer)) {
250
279
  return;
251
280
  }
252
281
  disconnectResizeObserver();
253
282
  resizeObserver = new ResizeObserver(() => {
254
- scheduleAdjustmentUpdate();
283
+ scheduleMeasurement();
255
284
  });
256
- observedContainer = startSlot;
257
- resizeObserver.observe(startSlot);
285
+ observedStartContainer = startContainer;
286
+ resizeObserver.observe(startContainer);
287
+ if (endContainer) {
288
+ observedEndContainer = endContainer;
289
+ resizeObserver.observe(endContainer);
290
+ }
258
291
  };
259
- const calculateStartContainerWidth = () => {
260
- scheduleAdjustmentUpdate();
292
+ const calculateContainerWidths = () => {
293
+ scheduleMeasurement();
261
294
  };
262
295
  const destroy = () => {
263
296
  disconnectResizeObserver();
@@ -273,9 +306,10 @@ const createStartContainerController = (el, getStartContainer, shouldApplyAdjust
273
306
  }
274
307
  // Re-apply from scratch if the element is connected again
275
308
  appliedAdjustment = undefined;
309
+ appliedStartEndWidth = undefined;
276
310
  };
277
311
  return {
278
- calculateStartContainerWidth,
312
+ calculateContainerWidths,
279
313
  destroy,
280
314
  };
281
315
  };
@@ -461,5 +495,5 @@ const createSlotMutationController = (el, slotName, mutationCallback) => {
461
495
  exports.createClickController = createClickController;
462
496
  exports.createNotchController = createNotchController;
463
497
  exports.createSlotMutationController = createSlotMutationController;
464
- exports.createStartContainerController = createStartContainerController;
498
+ exports.createStartEndContainerController = createStartEndContainerController;
465
499
  exports.getSlottedClickContent = getSlottedClickContent;
@@ -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`."
@@ -425,7 +425,11 @@
425
425
  * would not be able to see what they are typing.
426
426
  */
427
427
  max-width: 200px;
428
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
428
+ /**
429
+ * The max-width animates with the transform so the label does not
430
+ * reveal its full floating width while it is still at full size.
431
+ */
432
+ transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1), max-width 150ms cubic-bezier(0.4, 0, 0.2, 1);
429
433
  /**
430
434
  * This ensures that double tapping this text
431
435
  * clicks the <label> and focuses the input
@@ -439,8 +443,8 @@
439
443
  * disable the label transition to prevent the floating label
440
444
  * from animating as its position is adjusted.
441
445
  *
442
- * This class is applied by the start container controller, so
443
- * renaming it here requires renaming it there too.
446
+ * This class is applied by the start and end container controller,
447
+ * so renaming it here requires renaming it there too.
444
448
  */
445
449
  :host(.skip-label-transition) .label-text-wrapper {
446
450
  transition: none;
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Build, Host, forceUpdate, h, } from "@stencil/core";
5
- import { createClickController, createNotchController, createStartContainerController, checkInvalidState, } from "../../utils/forms/index";
5
+ import { createClickController, createNotchController, createStartEndContainerController, checkInvalidState, } from "../../utils/forms/index";
6
6
  import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
7
7
  import { createSlotMutationController } from "../../utils/slot-mutation-controller";
8
8
  import { createColorClasses, hostContext } from "../../utils/theme";
@@ -238,16 +238,16 @@ export class Input {
238
238
  connectedCallback() {
239
239
  const { el } = this;
240
240
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => {
241
- this.startContainerController?.calculateStartContainerWidth();
241
+ this.startEndContainerController?.calculateContainerWidths();
242
242
  this.setSlottedLabelId();
243
243
  forceUpdate(this);
244
244
  });
245
245
  this.setSlottedLabelId();
246
246
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
247
- this.startContainerController = createStartContainerController(el, () => this.startContainerEl, () => {
247
+ this.startEndContainerController = createStartEndContainerController(el, () => this.startContainerEl, () => this.endContainerEl, () => {
248
248
  return Build.isBrowser && getIonMode(this) === 'md' && this.fill === 'outline';
249
249
  });
250
- this.startContainerController.calculateStartContainerWidth();
250
+ this.startEndContainerController.calculateContainerWidths();
251
251
  this.clickController = createClickController(el, () => this.nativeInput);
252
252
  // Watch for class changes to update validation state
253
253
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
@@ -286,7 +286,7 @@ export class Input {
286
286
  }
287
287
  componentDidRender() {
288
288
  this.notchController?.calculateNotchWidth();
289
- this.startContainerController?.calculateStartContainerWidth();
289
+ this.startEndContainerController?.calculateContainerWidths();
290
290
  }
291
291
  disconnectedCallback() {
292
292
  if (Build.isBrowser) {
@@ -302,9 +302,9 @@ export class Input {
302
302
  this.notchController.destroy();
303
303
  this.notchController = undefined;
304
304
  }
305
- if (this.startContainerController) {
306
- this.startContainerController.destroy();
307
- this.startContainerController = undefined;
305
+ if (this.startEndContainerController) {
306
+ this.startEndContainerController.destroy();
307
+ this.startEndContainerController = undefined;
308
308
  }
309
309
  // Clean up validation observer to prevent memory leaks
310
310
  if (this.validationObserver) {
@@ -524,7 +524,7 @@ export class Input {
524
524
  * the input has a value or is focused.
525
525
  */
526
526
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus));
527
- return (h(Host, { key: '290cf987de670276bf40b9f19d35d3769d9c0540', class: createColorClasses(this.color, {
527
+ return (h(Host, { key: '0d03d37173639105b19083c0b44da25d9115a449', class: createColorClasses(this.color, {
528
528
  [mode]: true,
529
529
  'has-value': hasValue,
530
530
  'has-focus': hasFocus,
@@ -535,14 +535,14 @@ export class Input {
535
535
  'in-item': inItem,
536
536
  'in-item-color': hostContext('ion-item.ion-color', this.el),
537
537
  'input-disabled': disabled,
538
- }) }, h("label", { key: '27f4bd671f6084a7e608e4f9442192c950879dc2', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '7827c61ffdd02952fc8cf2096f621b50fc51ec03', class: "input-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '1d8af52d3506742d87334b388859a8bf613e1be0', name: "start" })), h("div", { key: '927a412064538c00df98fabc06a32c564d05e984', class: "input-control" }, this.renderLabel(), h("div", { key: '05f80c0b11a7da57445f976bd2dbb557fa63c9bc', class: "native-wrapper", onClick: this.onLabelClick }, h("input", { key: 'cb6f5d81ba7c2a04cc4c9deb5d3f5bca8562b6db', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect ? 'on' : 'off', autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": this.getLabelledById(), ...this.inheritedAttributes }))), h("div", { key: '82cd1b0458dcd491fd1a46b29764f2b79a969bc6', class: "input-end" }, this.clearInput && !readonly && !disabled && (h("button", { key: 'dfcf0ed81cf36fc61b8e12ed4605ac6b6eb50dad', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
538
+ }) }, h("label", { key: '1d62890b6a54229fccae0832eebe92e546e6e53d', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: 'b1ceeb7666678569f6479729d556a29b8e549ce5', class: "input-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '1af55c89b1390ff9063a6c6d49a1e8bd01c7c78e', name: "start" })), h("div", { key: '6df6bc5521352ca87aeb400d169b0c9129521249', class: "input-control" }, this.renderLabel(), h("div", { key: '55d2b3a235332d7fbf61356b13052b5c9cda01de', class: "native-wrapper", onClick: this.onLabelClick }, h("input", { key: 'a938cb7ef2e87bde9da310817e1a48e7a5c67bc3', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect ? 'on' : 'off', autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": this.getLabelledById(), ...this.inheritedAttributes }))), h("div", { key: 'c99685e001ee133220d74cfa877a72063d46433c', class: "input-end", ref: (el) => (this.endContainerEl = el) }, this.clearInput && !readonly && !disabled && (h("button", { key: '0e0e154182cca8b5367c1586b3c8c5b20b38006b', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
539
539
  /**
540
540
  * This prevents mobile browsers from
541
541
  * blurring the input when the clear
542
542
  * button is activated.
543
543
  */
544
544
  ev.preventDefault();
545
- }, onClick: this.clearTextInput }, h("ion-icon", { key: '11e2c866135f332e81b4637de178e1c5c7da0d38', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: 'afb0e75e7103b2bf925a9c7649c93eb1d5702670', name: "end" })), shouldRenderHighlight && h("div", { key: '1f305bba279e93685d7139acf0aff6bf4a77500c', class: "input-highlight" })), this.renderBottomContent()));
545
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: '655e8d25940dbfb96e7e83b6e89575337e3e91d3', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '6104c04ae34a26da62095de70c752efb2a0417ab', name: "end" })), shouldRenderHighlight && h("div", { key: 'c85cd8e07397e0bbd1e5a120014d83f391dfbc4c', class: "input-highlight" })), this.renderBottomContent()));
546
546
  }
547
547
  static get is() { return "ion-input"; }
548
548
  static get encapsulation() { return "scoped"; }
@@ -425,7 +425,11 @@
425
425
  * would not be able to see what they are typing.
426
426
  */
427
427
  max-width: 200px;
428
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
428
+ /**
429
+ * The max-width animates with the transform so the label does not
430
+ * reveal its full floating width while it is still at full size.
431
+ */
432
+ transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1), max-width 150ms cubic-bezier(0.4, 0, 0.2, 1);
429
433
  /**
430
434
  * This ensures that double tapping this text
431
435
  * clicks the <label> and focuses the input
@@ -439,8 +443,8 @@
439
443
  * disable the label transition to prevent the floating label
440
444
  * from animating as its position is adjusted.
441
445
  *
442
- * This class is applied by the start container controller, so
443
- * renaming it here requires renaming it there too.
446
+ * This class is applied by the start and end container controller,
447
+ * so renaming it here requires renaming it there too.
444
448
  */
445
449
  :host(.skip-label-transition) .label-text-wrapper {
446
450
  transition: none;
@@ -946,12 +950,20 @@
946
950
  --padding-end: 16px;
947
951
  /**
948
952
  * The measured width of the start container, as a negative offset
949
- * in LTR and a positive one in RTL. Written to the host by the
950
- * start container controller, so renaming it here requires renaming
953
+ * in LTR and a positive one in RTL. Written to the host by the start
954
+ * and end container controller, so renaming it here requires renaming
951
955
  * it there too. Declared with a default so the label is positioned
952
956
  * correctly before the first measurement runs.
953
957
  */
954
958
  --internal-start-container-adjustment: 0px;
959
+ /**
960
+ * The measured combined width of the start and end containers.
961
+ * Written to the host by the start and end container controller, so
962
+ * renaming it here requires renaming it there too. Declared with a
963
+ * default so the floating label falls back to the width of the
964
+ * control before the first measurement runs.
965
+ */
966
+ --internal-start-end-container-width: 0px;
955
967
  min-height: 56px;
956
968
  }
957
969
 
@@ -1007,11 +1019,12 @@
1007
1019
  }
1008
1020
 
1009
1021
  /**
1010
- * The resting label is positioned relative to the control so it can size to
1011
- * the space between the start and end slots. When floating, the label needs
1012
- * to escape the control and position itself relative to the wrapper instead.
1022
+ * The label is positioned relative to the control whether it is resting
1023
+ * over the input or floating in the notch, so its max-width always resolves
1024
+ * against the same width. This lets the max-width animate between the
1025
+ * resting and floating values without jumping.
1013
1026
  */
1014
- :host(.input-fill-outline:not(.label-floating)) .input-control {
1027
+ :host(.input-fill-outline) .input-control {
1015
1028
  position: relative;
1016
1029
  }
1017
1030
 
@@ -1020,9 +1033,8 @@
1020
1033
  transform-origin: left top;
1021
1034
  position: absolute;
1022
1035
  /**
1023
- * While resting this is the width of the text input, which already
1024
- * excludes the start and end slots. Once the label floats the
1025
- * containing block becomes the wrapper and the rule below takes over.
1036
+ * This is the width of the text input, which already
1037
+ * excludes the start and end slots.
1026
1038
  */
1027
1039
  max-width: 100%;
1028
1040
  }
@@ -1055,10 +1067,11 @@
1055
1067
  margin-top: 0;
1056
1068
  margin-bottom: 0;
1057
1069
  /**
1058
- * Label text should not extend
1059
- * beyond the bounds of the input.
1070
+ * Label text should not extend beyond the bounds of the input.
1071
+ * The control excludes the start and end slots, but the notch spans
1072
+ * the full width of the outline, so their width is added back.
1060
1073
  */
1061
- max-width: calc((100% - var(--padding-start) - var(--padding-end) - 8px) / 0.75);
1074
+ max-width: calc((100% + var(--internal-start-end-container-width) - 8px) / 0.75);
1062
1075
  }
1063
1076
 
1064
1077
  :host(.input-fill-outline) .input-outline-container {