@ionic/core 9.0.8 → 9.0.9-nightly.20261016

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 (90) hide show
  1. package/components/index.js +1 -1
  2. package/components/ion-datetime.js +1 -1
  3. package/components/ion-input.js +1 -1
  4. package/components/ion-modal.js +1 -1
  5. package/components/ion-nav.js +1 -1
  6. package/components/ion-popover.js +1 -1
  7. package/components/ion-router-outlet.js +1 -1
  8. package/components/ion-select.js +1 -1
  9. package/components/ion-textarea.js +1 -1
  10. package/components/p-Bd8hg9FK.js +4 -0
  11. package/components/{p-D64Tyi-z.js → p-CQKWzUsE.js} +1 -1
  12. package/components/{p-BFoLG0MR.js → p-CQnEg-p2.js} +1 -1
  13. package/components/{p-Dvyv9SMk.js → p-D0nwlsl_.js} +1 -1
  14. package/components/p-DfDIY2oP.js +4 -0
  15. package/components/p-SHHIB5F0.js +4 -0
  16. package/css/core.css.map +1 -1
  17. package/css/ionic.bundle.css.map +1 -1
  18. package/dist/cjs/{index-DUiuw_fH.js → index-DCfr5hz-.js} +14 -2
  19. package/dist/cjs/index.cjs.js +3 -3
  20. package/dist/cjs/ion-app_8.cjs.entry.js +31 -3
  21. package/dist/cjs/ion-input.cjs.entry.js +13 -13
  22. package/dist/cjs/ion-modal.cjs.entry.js +1 -1
  23. package/dist/cjs/ion-nav_2.cjs.entry.js +24 -2
  24. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  25. package/dist/cjs/ion-select_3.cjs.entry.js +12 -12
  26. package/dist/cjs/ion-textarea.cjs.entry.js +12 -12
  27. package/dist/cjs/{ios.transition-D85_rHf5.js → ios.transition-Ca6XODo9.js} +1 -1
  28. package/dist/cjs/{md.transition-Ce9X1WXr.js → md.transition-D8SfNYEp.js} +1 -1
  29. package/dist/cjs/{slot-mutation-controller-ClDxh3EK.js → slot-mutation-controller-B2OfJEBH.js} +67 -33
  30. package/dist/collection/components/input/input.ios.css +7 -3
  31. package/dist/collection/components/input/input.js +11 -11
  32. package/dist/collection/components/input/input.md.css +28 -15
  33. package/dist/collection/components/nav/nav.js +24 -2
  34. package/dist/collection/components/router-outlet/router-outlet.js +31 -3
  35. package/dist/collection/components/select/select.ios.css +7 -3
  36. package/dist/collection/components/select/select.js +10 -10
  37. package/dist/collection/components/select/select.md.css +28 -15
  38. package/dist/collection/components/textarea/textarea.ios.css +7 -3
  39. package/dist/collection/components/textarea/textarea.js +10 -10
  40. package/dist/collection/components/textarea/textarea.md.css +28 -17
  41. package/dist/collection/utils/forms/index.js +1 -1
  42. package/dist/collection/utils/forms/{start-container-controller.js → start-end-container-controller.js} +66 -32
  43. package/dist/collection/utils/transition/index.js +11 -0
  44. package/dist/docs.json +1 -1
  45. package/dist/esm/{index-BBASprVu.js → index-z9vdZN7I.js} +14 -3
  46. package/dist/esm/index.js +3 -3
  47. package/dist/esm/ion-app_8.entry.js +31 -3
  48. package/dist/esm/ion-input.entry.js +13 -13
  49. package/dist/esm/ion-modal.entry.js +1 -1
  50. package/dist/esm/ion-nav_2.entry.js +24 -2
  51. package/dist/esm/ion-popover.entry.js +1 -1
  52. package/dist/esm/ion-select_3.entry.js +12 -12
  53. package/dist/esm/ion-textarea.entry.js +12 -12
  54. package/dist/esm/{ios.transition-B8netzsb.js → ios.transition-BkUGiDUz.js} +1 -1
  55. package/dist/esm/{md.transition-DNCSSiXk.js → md.transition-B6hlrWjR.js} +1 -1
  56. package/dist/esm/{slot-mutation-controller-hAS2uJ7c.js → slot-mutation-controller-Btpr9-9O.js} +67 -33
  57. package/dist/ionic/index.esm.js +1 -1
  58. package/dist/ionic/ionic.esm.js +1 -1
  59. package/dist/ionic/p-293cb1d5.entry.js +4 -0
  60. package/dist/ionic/p-52a16f94.entry.js +4 -0
  61. package/dist/ionic/p-67f004eb.entry.js +4 -0
  62. package/dist/ionic/{p-Bppj2PE0.js → p-BZTgGzGK.js} +1 -1
  63. package/dist/ionic/p-BpED_ep_.js +4 -0
  64. package/dist/ionic/{p-Bnc1L7Rk.js → p-CgcGX08t.js} +1 -1
  65. package/dist/ionic/p-Dk4Mdaru.js +4 -0
  66. package/dist/ionic/{p-96267112.entry.js → p-af8fb360.entry.js} +1 -1
  67. package/dist/ionic/{p-2124fd4f.entry.js → p-d2406889.entry.js} +1 -1
  68. package/dist/ionic/p-e8e69c21.entry.js +4 -0
  69. package/dist/ionic/{p-af56702e.entry.js → p-f2116a54.entry.js} +1 -1
  70. package/dist/types/components/input/input.d.ts +2 -1
  71. package/dist/types/components/nav/nav.d.ts +1 -0
  72. package/dist/types/components/router-outlet/router-outlet.d.ts +1 -0
  73. package/dist/types/components/select/select.d.ts +2 -1
  74. package/dist/types/components/textarea/textarea.d.ts +2 -1
  75. package/dist/types/utils/forms/index.d.ts +1 -1
  76. package/dist/types/utils/forms/start-end-container-controller.d.ts +44 -0
  77. package/dist/types/utils/transition/index.d.ts +6 -0
  78. package/hydrate/index.js +164 -69
  79. package/hydrate/index.mjs +164 -69
  80. package/package.json +1 -1
  81. package/components/p-C7r5Gja6.js +0 -4
  82. package/components/p-TLtgwTFF.js +0 -4
  83. package/components/p-Uk13YgEc.js +0 -4
  84. package/dist/ionic/p-27d96a9b.entry.js +0 -4
  85. package/dist/ionic/p-4d785062.entry.js +0 -4
  86. package/dist/ionic/p-8b79061e.entry.js +0 -4
  87. package/dist/ionic/p-B8IuTu_r.js +0 -4
  88. package/dist/ionic/p-DQXtveHv.js +0 -4
  89. package/dist/ionic/p-fcb04fb5.entry.js +0 -4
  90. package/dist/types/utils/forms/start-container-controller.d.ts +0 -34
@@ -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 {
@@ -5,7 +5,7 @@ import { h } from "@stencil/core";
5
5
  import { getTimeGivenProgression } from "../../utils/animation/cubic-bezier";
6
6
  import { assert } from "../../utils/helpers";
7
7
  import { printIonWarning } from "../../utils/logging/index";
8
- import { lifecycle, setPageHidden, transition } from "../../utils/transition/index";
8
+ import { blockSwipeClicks, lifecycle, setPageHidden, transition } from "../../utils/transition/index";
9
9
  import { config } from "../../global/config";
10
10
  import { getIonMode } from "../../global/ionic-global";
11
11
  import { LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_LEAVE, LIFECYCLE_WILL_UNLOAD } from "./constants";
@@ -68,6 +68,12 @@ export class Nav {
68
68
  this.gesture.destroy();
69
69
  this.gesture = undefined;
70
70
  }
71
+ // Destroying the gesture skips onEnd, so end a deferred swipe here.
72
+ if (this.unblockSwipeClicks) {
73
+ this.unblockSwipeClicks();
74
+ this.unblockSwipeClicks = undefined;
75
+ this.gestureOrAnimationInProgress = false;
76
+ }
71
77
  this.transInstr.length = 0;
72
78
  this.views.length = 0;
73
79
  this.destroyed = true;
@@ -688,6 +694,14 @@ export class Nav {
688
694
  }
689
695
  onStart() {
690
696
  this.gestureOrAnimationInProgress = true;
697
+ /**
698
+ * Without animations there's nothing for the swipe to drive, so
699
+ * wait until the gesture ends to decide whether to go back.
700
+ */
701
+ if (!(this.animated && config.getBoolean('animated', true))) {
702
+ this.unblockSwipeClicks = blockSwipeClicks(this.el);
703
+ return;
704
+ }
691
705
  this.pop({ direction: 'back', progressAnimation: true });
692
706
  }
693
707
  onMove(stepValue) {
@@ -721,10 +735,18 @@ export class Nav {
721
735
  }
722
736
  else {
723
737
  this.gestureOrAnimationInProgress = false;
738
+ const { unblockSwipeClicks } = this;
739
+ if (unblockSwipeClicks) {
740
+ this.unblockSwipeClicks = undefined;
741
+ if (shouldComplete) {
742
+ this.pop({ direction: 'back' });
743
+ }
744
+ unblockSwipeClicks();
745
+ }
724
746
  }
725
747
  }
726
748
  render() {
727
- return h("slot", { key: '1886154d3c2fa8511af4d641d269aaa4c0f45c05' });
749
+ return h("slot", { key: '0aea554c0cf00dd3829757ec74c3855eccf1e068' });
728
750
  }
729
751
  static get is() { return "ion-nav"; }
730
752
  static get encapsulation() { return "shadow"; }
@@ -7,7 +7,7 @@ import { attachComponent, detachComponent } from "../../utils/framework-delegate
7
7
  import { shallowEqualStringMap, hasLazyBuild } from "../../utils/helpers";
8
8
  import { createLockController } from "../../utils/lock-controller";
9
9
  import { printIonError } from "../../utils/logging/index";
10
- import { transition } from "../../utils/transition/index";
10
+ import { blockSwipeClicks, transition } from "../../utils/transition/index";
11
11
  import { config } from "../../global/config";
12
12
  import { getIonMode } from "../../global/ionic-global";
13
13
  export class RouterOutlet {
@@ -32,6 +32,14 @@ export class RouterOutlet {
32
32
  async connectedCallback() {
33
33
  const onStart = () => {
34
34
  this.gestureOrAnimationInProgress = true;
35
+ /**
36
+ * Without animations there's nothing for the swipe to drive, so
37
+ * wait until the gesture ends to decide whether to go back.
38
+ */
39
+ if (!(this.animated && config.getBoolean('animated', true))) {
40
+ this.unblockSwipeClicks = blockSwipeClicks(this.el);
41
+ return;
42
+ }
35
43
  if (this.swipeHandler) {
36
44
  this.swipeHandler.onStart();
37
45
  }
@@ -64,7 +72,21 @@ export class RouterOutlet {
64
72
  this.ani.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
65
73
  }
66
74
  else {
67
- this.gestureOrAnimationInProgress = false;
75
+ const { swipeHandler, unblockSwipeClicks: unblock } = this;
76
+ this.unblockSwipeClicks = undefined;
77
+ // The stack can change during the swipe, so check that going back is still possible.
78
+ if (unblock && shouldComplete && swipeHandler?.canStart()) {
79
+ new Promise((resolve) => resolve(swipeHandler.onStart()))
80
+ .then(() => swipeHandler.onEnd(true))
81
+ .finally(() => {
82
+ this.gestureOrAnimationInProgress = false;
83
+ unblock();
84
+ });
85
+ }
86
+ else {
87
+ this.gestureOrAnimationInProgress = false;
88
+ unblock?.();
89
+ }
68
90
  }
69
91
  });
70
92
  if (this.swipeGesture === undefined) {
@@ -87,6 +109,12 @@ export class RouterOutlet {
87
109
  this.gesture.destroy();
88
110
  this.gesture = undefined;
89
111
  }
112
+ // Destroying the gesture skips onEnd, so end a deferred swipe here.
113
+ if (this.unblockSwipeClicks) {
114
+ this.unblockSwipeClicks();
115
+ this.unblockSwipeClicks = undefined;
116
+ this.gestureOrAnimationInProgress = false;
117
+ }
90
118
  }
91
119
  /** @internal */
92
120
  async commit(enteringEl, leavingEl, opts) {
@@ -198,7 +226,7 @@ export class RouterOutlet {
198
226
  return true;
199
227
  }
200
228
  render() {
201
- return h("slot", { key: '249e7d8a063c781db52fa535e42d648c7bf2909a' });
229
+ return h("slot", { key: '078cdc7a63b98570746409fc08c4af80f8d7da4d' });
202
230
  }
203
231
  static get is() { return "ion-router-outlet"; }
204
232
  static get encapsulation() { return "shadow"; }
@@ -390,7 +390,11 @@ button {
390
390
  * would not be able to see what they are typing.
391
391
  */
392
392
  max-width: 200px;
393
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
393
+ /**
394
+ * The max-width animates with the transform so the label does not
395
+ * reveal its full floating width while it is still at full size.
396
+ */
397
+ 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);
394
398
  /**
395
399
  * This ensures that double tapping this text
396
400
  * clicks the <label> and focuses the input
@@ -404,8 +408,8 @@ button {
404
408
  * disable the label transition to prevent the floating label
405
409
  * from animating as its position is adjusted.
406
410
  *
407
- * This class is applied by the start container controller, so
408
- * renaming it here requires renaming it there too.
411
+ * This class is applied by the start and end container controller,
412
+ * so renaming it here requires renaming it there too.
409
413
  */
410
414
  :host(.skip-label-transition) .label-text-wrapper {
411
415
  transition: none;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { Build, Host, h, forceUpdate, } from "@stencil/core";
5
5
  import { ENABLE_HTML_CONTENT_DEFAULT } from "../../utils/config";
6
- import { compareOptions, createClickController, createNotchController, createStartContainerController, getSlottedClickContent, isOptionSelected, checkInvalidState, } from "../../utils/forms/index";
6
+ import { compareOptions, createClickController, createNotchController, createStartEndContainerController, getSlottedClickContent, isOptionSelected, checkInvalidState, } from "../../utils/forms/index";
7
7
  import { focusVisibleElement, renderHiddenInput, inheritAttributes } from "../../utils/helpers";
8
8
  import { printIonWarning } from "../../utils/logging/index";
9
9
  import { actionSheetController, alertController, popoverController, modalController } from "../../utils/overlays";
@@ -186,14 +186,14 @@ export class Select {
186
186
  async connectedCallback() {
187
187
  const { el } = this;
188
188
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => {
189
- this.startContainerController?.calculateStartContainerWidth();
189
+ this.startEndContainerController?.calculateContainerWidths();
190
190
  forceUpdate(this);
191
191
  });
192
192
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
193
- this.startContainerController = createStartContainerController(el, () => this.startContainerEl, () => {
193
+ this.startEndContainerController = createStartEndContainerController(el, () => this.startContainerEl, () => this.endContainerEl, () => {
194
194
  return Build.isBrowser && getIonMode(this) === 'md' && this.fill === 'outline';
195
195
  });
196
- this.startContainerController.calculateStartContainerWidth();
196
+ this.startEndContainerController.calculateContainerWidths();
197
197
  this.clickController = createClickController(el);
198
198
  this.updateOverlayOptions();
199
199
  this.emitStyle();
@@ -274,9 +274,9 @@ export class Select {
274
274
  this.notchController.destroy();
275
275
  this.notchController = undefined;
276
276
  }
277
- if (this.startContainerController) {
278
- this.startContainerController.destroy();
279
- this.startContainerController = undefined;
277
+ if (this.startEndContainerController) {
278
+ this.startEndContainerController.destroy();
279
+ this.startEndContainerController = undefined;
280
280
  }
281
281
  // Clean up validation observer to prevent memory leaks.
282
282
  if (this.validationObserver) {
@@ -752,7 +752,7 @@ export class Select {
752
752
  }
753
753
  componentDidRender() {
754
754
  this.notchController?.calculateNotchWidth();
755
- this.startContainerController?.calculateStartContainerWidth();
755
+ this.startEndContainerController?.calculateContainerWidths();
756
756
  }
757
757
  /**
758
758
  * Gets any content passed into the `label` slot,
@@ -940,7 +940,7 @@ export class Select {
940
940
  const hasOutlineFill = mode === 'md' && fill === 'outline';
941
941
  renderHiddenInput(true, el, name, parseValue(value), disabled);
942
942
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || isExpanded));
943
- return (h(Host, { key: '31585557ac49c439939b5a084867f5039dc2f1ce', onClick: this.onClick, class: createColorClasses(this.color, {
943
+ return (h(Host, { key: 'c1949b8a58c5bde1cc7627d3b32c54bcb46a4752', onClick: this.onClick, class: createColorClasses(this.color, {
944
944
  [mode]: true,
945
945
  'in-item': inItem,
946
946
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -958,7 +958,7 @@ export class Select {
958
958
  [`select-justify-${justify}`]: justifyEnabled,
959
959
  [`select-shape-${shape}`]: shape !== undefined,
960
960
  [`select-label-placement-${labelPlacement}`]: true,
961
- }) }, h("label", { key: '368f5f0c77550be6a743752dd1acbfe9915f0a3d', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '2e9039eaa15f398ebe54808ff659db89d3621681', class: "select-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'b83e362f89bd8cbc564ad7122778e3a0f08be66d', name: "start" })), h("div", { key: 'f55ac9fc3bbd9a4a3819a101a3780fc02ae8a8e0', class: "select-control", part: "control" }, this.renderLabel(), h("div", { key: 'e32984e57cda5cf341af8e354a2aef5a114827c0', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox(), !hasFloatingOrStackedLabel && this.renderSelectIcon())), h("div", { key: 'fc4cb7c392c46129afe709d9557ad9a10d72b564', class: "select-end", part: "end" }, hasFloatingOrStackedLabel && this.renderSelectIcon(), h("slot", { key: '6d65c92cdafe985a769fb3b41a58625077803eef', name: "end" })), shouldRenderHighlight && h("div", { key: '9a912c64d7cd77f074a069d126cd0bada1b71eed', class: "select-highlight" })), this.renderBottomContent()));
961
+ }) }, h("label", { key: 'af4521a3679d9e4e08511e205f53663d8d5b6701', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '73e13f28ed4c2b0e9115f3d1b86934b84f8c5bc0', class: "select-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'ce88e5bbb15a4cde6a5f8580a35d7099e315e9e6', name: "start" })), h("div", { key: 'd1557c3d0ff41f5070a27b719fbfaf18eafa9052', class: "select-control", part: "control" }, this.renderLabel(), h("div", { key: 'bcd03ac6c6d72ab76a4344069d83c29a2cdaabf0', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox(), !hasFloatingOrStackedLabel && this.renderSelectIcon())), h("div", { key: '9be05035c1efb5c332a0044c81e032215c81278b', class: "select-end", part: "end", ref: (el) => (this.endContainerEl = el) }, hasFloatingOrStackedLabel && this.renderSelectIcon(), h("slot", { key: '551ab0cf3594805caaf14f36197f9cc56b9ee46a', name: "end" })), shouldRenderHighlight && h("div", { key: 'dd500fb4ae7d91d1733fec521dac9baaef6654a0', class: "select-highlight" })), this.renderBottomContent()));
962
962
  }
963
963
  static get is() { return "ion-select"; }
964
964
  static get encapsulation() { return "shadow"; }
@@ -390,7 +390,11 @@ button {
390
390
  * would not be able to see what they are typing.
391
391
  */
392
392
  max-width: 200px;
393
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
393
+ /**
394
+ * The max-width animates with the transform so the label does not
395
+ * reveal its full floating width while it is still at full size.
396
+ */
397
+ 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);
394
398
  /**
395
399
  * This ensures that double tapping this text
396
400
  * clicks the <label> and focuses the input
@@ -404,8 +408,8 @@ button {
404
408
  * disable the label transition to prevent the floating label
405
409
  * from animating as its position is adjusted.
406
410
  *
407
- * This class is applied by the start container controller, so
408
- * renaming it here requires renaming it there too.
411
+ * This class is applied by the start and end container controller,
412
+ * so renaming it here requires renaming it there too.
409
413
  */
410
414
  :host(.skip-label-transition) .label-text-wrapper {
411
415
  transition: none;
@@ -981,12 +985,20 @@ button {
981
985
  --padding-end: 16px;
982
986
  /**
983
987
  * The measured width of the start container, as a negative offset
984
- * in LTR and a positive one in RTL. Written to the host by the
985
- * start container controller, so renaming it here requires renaming
988
+ * in LTR and a positive one in RTL. Written to the host by the start
989
+ * and end container controller, so renaming it here requires renaming
986
990
  * it there too. Declared with a default so the label is positioned
987
991
  * correctly before the first measurement runs.
988
992
  */
989
993
  --internal-start-container-adjustment: 0px;
994
+ /**
995
+ * The measured combined width of the start and end containers.
996
+ * Written to the host by the start and end container controller, so
997
+ * renaming it here requires renaming it there too. Declared with a
998
+ * default so the floating label falls back to the width of the
999
+ * control before the first measurement runs.
1000
+ */
1001
+ --internal-start-end-container-width: 0px;
990
1002
  min-height: 56px;
991
1003
  }
992
1004
 
@@ -1056,9 +1068,8 @@ button {
1056
1068
  :host(.select-fill-outline.select-label-placement-floating) .label-text-wrapper {
1057
1069
  position: absolute;
1058
1070
  /**
1059
- * While resting this is the width of the select, which already
1060
- * excludes the start and end slots. Once the label floats the
1061
- * containing block becomes the wrapper and the rule below takes over.
1071
+ * This is the width of the select, which already
1072
+ * excludes the start and end slots.
1062
1073
  */
1063
1074
  max-width: 100%;
1064
1075
  }
@@ -1074,11 +1085,12 @@ button {
1074
1085
  }
1075
1086
 
1076
1087
  /**
1077
- * The resting label is positioned relative to the control so it can size to
1078
- * the space between the start and end slots. When floating, the label needs
1079
- * to escape the control and position itself relative to the wrapper instead.
1088
+ * The label is positioned relative to the control whether it is resting
1089
+ * over the select or floating in the notch, so its max-width always resolves
1090
+ * against the same width. This lets the max-width animate between the
1091
+ * resting and floating values without jumping.
1080
1092
  */
1081
- :host(.select-fill-outline:not(.label-floating)) .select-control {
1093
+ :host(.select-fill-outline) .select-control {
1082
1094
  position: relative;
1083
1095
  }
1084
1096
 
@@ -1092,10 +1104,11 @@ button {
1092
1104
  margin-top: 0;
1093
1105
  margin-bottom: 0;
1094
1106
  /**
1095
- * Label text should not extend
1096
- * beyond the bounds of the select.
1107
+ * Label text should not extend beyond the bounds of the select.
1108
+ * The control excludes the start and end slots, but the notch spans
1109
+ * the full width of the outline, so their width is added back.
1097
1110
  */
1098
- max-width: calc((100% - var(--padding-start) - var(--padding-end) - 8px) / 0.75);
1111
+ max-width: calc((100% + var(--internal-start-end-container-width) - 8px) / 0.75);
1099
1112
  }
1100
1113
 
1101
1114
  /**
@@ -453,7 +453,11 @@
453
453
  * would not be able to see what they are typing.
454
454
  */
455
455
  max-width: 200px;
456
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
456
+ /**
457
+ * The max-width animates with the transform so the label does not
458
+ * reveal its full floating width while it is still at full size.
459
+ */
460
+ 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);
457
461
  /**
458
462
  * This ensures that double tapping this text
459
463
  * clicks the <label> and focuses the textarea
@@ -467,8 +471,8 @@
467
471
  * disable the label transition to prevent the floating label
468
472
  * from animating as its position is adjusted.
469
473
  *
470
- * This class is applied by the start container controller, so
471
- * renaming it here requires renaming it there too.
474
+ * This class is applied by the start and end container controller,
475
+ * so renaming it here requires renaming it there too.
472
476
  */
473
477
  :host(.skip-label-transition) .label-text-wrapper {
474
478
  transition: none;
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Build, Host, forceUpdate, h, writeTask, } 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";
@@ -194,14 +194,14 @@ export class Textarea {
194
194
  connectedCallback() {
195
195
  const { el } = this;
196
196
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => {
197
- this.startContainerController?.calculateStartContainerWidth();
197
+ this.startEndContainerController?.calculateContainerWidths();
198
198
  forceUpdate(this);
199
199
  });
200
200
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
201
- this.startContainerController = createStartContainerController(el, () => this.startContainerEl, () => {
201
+ this.startEndContainerController = createStartEndContainerController(el, () => this.startContainerEl, () => this.endContainerEl, () => {
202
202
  return Build.isBrowser && getIonMode(this) === 'md' && this.fill === 'outline';
203
203
  });
204
- this.startContainerController.calculateStartContainerWidth();
204
+ this.startEndContainerController.calculateContainerWidths();
205
205
  this.clickController = createClickController(el, () => this.nativeInput);
206
206
  // Watch for class changes to update validation state
207
207
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
@@ -241,9 +241,9 @@ export class Textarea {
241
241
  this.notchController.destroy();
242
242
  this.notchController = undefined;
243
243
  }
244
- if (this.startContainerController) {
245
- this.startContainerController.destroy();
246
- this.startContainerController = undefined;
244
+ if (this.startEndContainerController) {
245
+ this.startEndContainerController.destroy();
246
+ this.startEndContainerController = undefined;
247
247
  }
248
248
  // Clean up validation observer to prevent memory leaks
249
249
  if (this.validationObserver) {
@@ -263,7 +263,7 @@ export class Textarea {
263
263
  }
264
264
  componentDidRender() {
265
265
  this.notchController?.calculateNotchWidth();
266
- this.startContainerController?.calculateStartContainerWidth();
266
+ this.startEndContainerController?.calculateContainerWidths();
267
267
  }
268
268
  /**
269
269
  * Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global
@@ -458,7 +458,7 @@ export class Textarea {
458
458
  * the textarea has a value or is focused.
459
459
  */
460
460
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus));
461
- return (h(Host, { key: '0f01ed6565313c45beaea5e0aa429a86b9cfa1a0', class: createColorClasses(this.color, {
461
+ return (h(Host, { key: '59f577d7b82036d5e12d7b4e7d5f0339af1d185a', class: createColorClasses(this.color, {
462
462
  [mode]: true,
463
463
  'has-value': hasValue,
464
464
  'has-focus': hasFocus,
@@ -467,7 +467,7 @@ export class Textarea {
467
467
  [`textarea-shape-${shape}`]: shape !== undefined,
468
468
  [`textarea-label-placement-${labelPlacement}`]: true,
469
469
  'textarea-disabled': disabled,
470
- }) }, h("label", { key: 'a59548c3d3bf66543fd6e06b5c3b6e649f963164', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '29055193d664d9fe84ba0340fdcbbc54fec0e403', class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '2cc122d9541ce7ae3c2309c27123b6c6c91cbfc7', name: "start" })), h("div", { key: 'c847a6fa1eaf6ef2b296415cd72bdd3aee9828a2', class: "textarea-control" }, this.renderLabel(), h("div", { key: 'f9205f82d6346daa86dc7e0f6e1eb221c76ed7b0', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", { key: '0bdb517fa5b3a44eeb3bb2ed1f9a8eab21a9a6a1', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, ...this.inheritedAttributes }, value))), h("div", { key: '1ce661b800ef12146bdb97fb193e22be227ea052', class: "textarea-end" }, h("slot", { key: 'aca5704db48fe8e0e784cf57f2978ae2d63b3edc', name: "end" })), shouldRenderHighlight && h("div", { key: 'd916816d44cbdb4bc9a05032c5490eaa9bf3ba47', class: "textarea-highlight" })), this.renderBottomContent()));
470
+ }) }, h("label", { key: '4684c4e760f0272d377809fcb28ed6fcad48b542', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: 'a3dd0e7b9494fce1ee33d011138600e8b697b227', class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '8f0a36f2373c859039b1437a0fdf8e7bef278272', name: "start" })), h("div", { key: '2cb3def25a6447aefb656e5fd89716be280055f1', class: "textarea-control" }, this.renderLabel(), h("div", { key: '613caadf5331ff4d9b52e1d2b22a7da36b248e76', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", { key: '3015a96367fa845f068118c5ae2484c4c8f30931', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, ...this.inheritedAttributes }, value))), h("div", { key: 'f97f361e9339125407b0ef5f53d8c55021f1f824', class: "textarea-end", ref: (el) => (this.endContainerEl = el) }, h("slot", { key: '032bd68ec3603f8cf598225714e02447712a283a', name: "end" })), shouldRenderHighlight && h("div", { key: '8e27cee63918d2f40a46c442a3bb2802be4cf3e4', class: "textarea-highlight" })), this.renderBottomContent()));
471
471
  }
472
472
  static get is() { return "ion-textarea"; }
473
473
  static get encapsulation() { return "scoped"; }
@@ -453,7 +453,11 @@
453
453
  * would not be able to see what they are typing.
454
454
  */
455
455
  max-width: 200px;
456
- transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
456
+ /**
457
+ * The max-width animates with the transform so the label does not
458
+ * reveal its full floating width while it is still at full size.
459
+ */
460
+ 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);
457
461
  /**
458
462
  * This ensures that double tapping this text
459
463
  * clicks the <label> and focuses the textarea
@@ -467,8 +471,8 @@
467
471
  * disable the label transition to prevent the floating label
468
472
  * from animating as its position is adjusted.
469
473
  *
470
- * This class is applied by the start container controller, so
471
- * renaming it here requires renaming it there too.
474
+ * This class is applied by the start and end container controller,
475
+ * so renaming it here requires renaming it there too.
472
476
  */
473
477
  :host(.skip-label-transition) .label-text-wrapper {
474
478
  transition: none;
@@ -1060,12 +1064,20 @@
1060
1064
  --padding-end: 16px;
1061
1065
  /**
1062
1066
  * The measured width of the start container, as a negative offset
1063
- * in LTR and a positive one in RTL. Written to the host by the
1064
- * start container controller, so renaming it here requires renaming
1067
+ * in LTR and a positive one in RTL. Written to the host by the start
1068
+ * and end container controller, so renaming it here requires renaming
1065
1069
  * it there too. Declared with a default so the label is positioned
1066
1070
  * correctly before the first measurement runs.
1067
1071
  */
1068
1072
  --internal-start-container-adjustment: 0px;
1073
+ /**
1074
+ * The measured combined width of the start and end containers.
1075
+ * Written to the host by the start and end container controller, so
1076
+ * renaming it here requires renaming it there too. Declared with a
1077
+ * default so the floating label falls back to the width of the
1078
+ * control before the first measurement runs.
1079
+ */
1080
+ --internal-start-end-container-width: 0px;
1069
1081
  }
1070
1082
 
1071
1083
  :host(.textarea-fill-outline.textarea-shape-round) {
@@ -1120,11 +1132,12 @@
1120
1132
  }
1121
1133
 
1122
1134
  /**
1123
- * The resting label is positioned relative to the control so it can size to
1124
- * the space between the start and end slots. When floating, the label needs
1125
- * to escape the control and position itself relative to the wrapper instead.
1135
+ * The label is positioned relative to the control whether it is resting
1136
+ * over the textarea or floating in the notch, so its max-width always resolves
1137
+ * against the same width. This lets the max-width animate between the
1138
+ * resting and floating values without jumping.
1126
1139
  */
1127
- :host(.textarea-fill-outline:not(.label-floating)) .textarea-control {
1140
+ :host(.textarea-fill-outline) .textarea-control {
1128
1141
  position: relative;
1129
1142
  }
1130
1143
 
@@ -1133,9 +1146,8 @@
1133
1146
  transform-origin: left top;
1134
1147
  position: absolute;
1135
1148
  /**
1136
- * While resting this is the width of the textarea, which already
1137
- * excludes the start and end slots. Once the label floats the
1138
- * containing block becomes the wrapper and the rule below takes over.
1149
+ * This is the width of the textarea, which already
1150
+ * excludes the start and end slots.
1139
1151
  */
1140
1152
  max-width: 100%;
1141
1153
  }
@@ -1168,12 +1180,11 @@
1168
1180
  margin-top: 0;
1169
1181
  margin-bottom: 0;
1170
1182
  /**
1171
- * Label text should not extend
1172
- * beyond the bounds of the textarea.
1183
+ * Label text should not extend beyond the bounds of the textarea.
1184
+ * The control excludes the start and end slots, but the notch spans
1185
+ * the full width of the outline, so their width is added back.
1173
1186
  */
1174
- max-width: calc(
1175
- (100% - var(--padding-start) - var(--padding-end) - 8px) / 0.75
1176
- );
1187
+ max-width: calc((100% + var(--internal-start-end-container-width) - 8px) / 0.75);
1177
1188
  }
1178
1189
 
1179
1190
  /**
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  export * from './notch-controller';
5
- export * from './start-container-controller';
5
+ export * from './start-end-container-controller';
6
6
  export * from './compare-with-utils';
7
7
  export * from './item-multiple-inputs';
8
8
  export * from './click-controller';