@ionic/core 9.0.3-dev.11788456135.1ab1d6cd → 9.0.3-dev.11790016799.1e269b7a

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 (183) hide show
  1. package/components/ion-accordion.js +1 -1
  2. package/components/ion-alert.js +1 -1
  3. package/components/ion-back-button.js +1 -1
  4. package/components/ion-breadcrumb.js +1 -1
  5. package/components/ion-checkbox.js +1 -1
  6. package/components/ion-datetime.js +1 -1
  7. package/components/ion-fab-button.js +1 -1
  8. package/components/ion-input-password-toggle.js +1 -1
  9. package/components/ion-input.js +1 -1
  10. package/components/ion-item.js +1 -1
  11. package/components/ion-menu-button.js +1 -1
  12. package/components/ion-refresher-content.js +1 -1
  13. package/components/ion-reorder.js +1 -1
  14. package/components/ion-searchbar.js +1 -1
  15. package/components/ion-select-modal.js +1 -1
  16. package/components/ion-select-popover.js +1 -1
  17. package/components/ion-select.js +1 -1
  18. package/components/ion-textarea.js +1 -1
  19. package/components/ion-toggle.js +1 -1
  20. package/components/{p-Cc3oC60a.js → p-0rXxxCTt.js} +1 -1
  21. package/components/p-B9ibhQu1.js +4 -0
  22. package/components/p-BzxHcuQH.js +4 -0
  23. package/components/p-C6wm_AQQ.js +4 -0
  24. package/components/p-CaxVJjmc.js +4 -0
  25. package/components/{p-Cm78Kk79.js → p-D2X-Hime.js} +1 -1
  26. package/css/ionic/bundle.ionic.css.map +1 -1
  27. package/css/ionic/core.ionic.css.map +1 -1
  28. package/css/ionic/global.bundle.ionic.css.map +1 -1
  29. package/css/ionic/ionic-swiper.ionic.css.map +1 -1
  30. package/css/ionic/link.ionic.css.map +1 -1
  31. package/css/ionic/typography.ionic.css.map +1 -1
  32. package/dist/cjs/ion-accordion_2.cjs.entry.js +9 -14
  33. package/dist/cjs/ion-alert.cjs.entry.js +4 -4
  34. package/dist/cjs/ion-back-button.cjs.entry.js +11 -13
  35. package/dist/cjs/ion-breadcrumb_2.cjs.entry.js +16 -29
  36. package/dist/cjs/ion-checkbox.cjs.entry.js +23 -15
  37. package/dist/cjs/ion-datetime.cjs.entry.js +14 -50
  38. package/dist/cjs/ion-fab_3.cjs.entry.js +9 -14
  39. package/dist/cjs/ion-input-password-toggle.cjs.entry.js +17 -29
  40. package/dist/cjs/ion-input.cjs.entry.js +12 -28
  41. package/dist/cjs/ion-item_8.cjs.entry.js +10 -15
  42. package/dist/cjs/ion-menu_3.cjs.entry.js +9 -12
  43. package/dist/cjs/ion-refresher_2.cjs.entry.js +10 -5
  44. package/dist/cjs/ion-reorder_2.cjs.entry.js +5 -13
  45. package/dist/cjs/ion-searchbar.cjs.entry.js +8 -37
  46. package/dist/cjs/ion-select-modal.cjs.entry.js +11 -10
  47. package/dist/cjs/ion-select_3.cjs.entry.js +13 -37
  48. package/dist/cjs/ion-textarea.cjs.entry.js +13 -21
  49. package/dist/cjs/ion-toggle.cjs.entry.js +12 -28
  50. package/dist/collection/components/accordion/accordion.js +9 -14
  51. package/dist/collection/components/alert/alert.ionic.css +7 -9
  52. package/dist/collection/components/alert/alert.js +3 -3
  53. package/dist/collection/components/back-button/back-button.js +11 -13
  54. package/dist/collection/components/breadcrumb/breadcrumb.js +16 -28
  55. package/dist/collection/components/checkbox/checkbox.ionic.css +35 -42
  56. package/dist/collection/components/checkbox/checkbox.ios.css +33 -29
  57. package/dist/collection/components/checkbox/checkbox.js +23 -13
  58. package/dist/collection/components/checkbox/checkbox.md.css +33 -29
  59. package/dist/collection/components/datetime/datetime.ios.css +5 -10
  60. package/dist/collection/components/datetime/datetime.js +12 -48
  61. package/dist/collection/components/datetime/datetime.md.css +5 -10
  62. package/dist/collection/components/fab-button/fab-button.js +9 -14
  63. package/dist/collection/components/input/input.ionic.css +102 -79
  64. package/dist/collection/components/input/input.js +11 -27
  65. package/dist/collection/components/input-password-toggle/input-password-toggle.js +18 -28
  66. package/dist/collection/components/item/item.js +10 -15
  67. package/dist/collection/components/menu-button/menu-button.js +9 -12
  68. package/dist/collection/components/refresher-content/refresher-content.js +10 -4
  69. package/dist/collection/components/reorder/reorder.js +5 -13
  70. package/dist/collection/components/searchbar/searchbar.js +8 -35
  71. package/dist/collection/components/select/select.ionic.css +187 -76
  72. package/dist/collection/components/select/select.js +14 -39
  73. package/dist/collection/components/select-modal/select-modal.js +12 -10
  74. package/dist/collection/components/textarea/textarea.ionic.css +133 -85
  75. package/dist/collection/components/textarea/textarea.ios.css +0 -4
  76. package/dist/collection/components/textarea/textarea.js +13 -18
  77. package/dist/collection/components/textarea/textarea.md.css +0 -4
  78. package/dist/collection/components/toggle/toggle.js +12 -26
  79. package/dist/collection/utils/test/playwright/field-geometry.js +34 -0
  80. package/dist/collection/utils/test/playwright/index.js +1 -0
  81. package/dist/docs.json +60 -25
  82. package/dist/esm/{index-BqQ15K9a.js → index-BkJpfpXI.js} +1 -1
  83. package/dist/esm/ion-accordion_2.entry.js +10 -15
  84. package/dist/esm/ion-alert.entry.js +4 -4
  85. package/dist/esm/ion-back-button.entry.js +12 -14
  86. package/dist/esm/ion-breadcrumb_2.entry.js +17 -30
  87. package/dist/esm/ion-checkbox.entry.js +24 -16
  88. package/dist/esm/ion-datetime.entry.js +15 -51
  89. package/dist/esm/ion-fab_3.entry.js +10 -15
  90. package/dist/esm/ion-input-password-toggle.entry.js +19 -31
  91. package/dist/esm/ion-input.entry.js +13 -29
  92. package/dist/esm/ion-item_8.entry.js +11 -16
  93. package/dist/esm/ion-menu_3.entry.js +10 -13
  94. package/dist/esm/ion-refresher_2.entry.js +11 -6
  95. package/dist/esm/ion-reorder_2.entry.js +6 -14
  96. package/dist/esm/ion-searchbar.entry.js +9 -38
  97. package/dist/esm/ion-select-modal.entry.js +13 -12
  98. package/dist/esm/ion-select_3.entry.js +14 -38
  99. package/dist/esm/ion-textarea.entry.js +13 -21
  100. package/dist/esm/ion-toggle.entry.js +13 -29
  101. package/dist/ionic/ionic.esm.js +1 -1
  102. package/dist/ionic/p-016f2ee0.entry.js +4 -0
  103. package/dist/ionic/p-07b95001.entry.js +4 -0
  104. package/dist/ionic/p-14dfe86e.entry.js +4 -0
  105. package/dist/ionic/p-167af5d6.entry.js +4 -0
  106. package/dist/ionic/p-18285aab.entry.js +4 -0
  107. package/dist/ionic/{p-2e3acd77.entry.js → p-1e0f0eb9.entry.js} +1 -1
  108. package/dist/ionic/p-368935f5.entry.js +4 -0
  109. package/dist/ionic/p-38da0794.entry.js +4 -0
  110. package/dist/ionic/p-80f71456.entry.js +4 -0
  111. package/dist/ionic/p-83aa4043.entry.js +4 -0
  112. package/dist/ionic/p-944ce631.entry.js +4 -0
  113. package/dist/ionic/{p-BqQ15K9a.js → p-BkJpfpXI.js} +1 -1
  114. package/dist/ionic/p-aecef1d7.entry.js +4 -0
  115. package/dist/ionic/p-bb83b861.entry.js +4 -0
  116. package/dist/ionic/p-c1c90068.entry.js +4 -0
  117. package/dist/ionic/p-ccc7487b.entry.js +4 -0
  118. package/dist/ionic/p-cfd5dc50.entry.js +4 -0
  119. package/dist/ionic/p-d5c9bf12.entry.js +4 -0
  120. package/dist/ionic/p-f252ab8a.entry.js +4 -0
  121. package/dist/types/components/accordion/accordion.d.ts +7 -1
  122. package/dist/types/components/back-button/back-button.d.ts +8 -2
  123. package/dist/types/components/breadcrumb/breadcrumb.d.ts +12 -2
  124. package/dist/types/components/checkbox/checkbox.d.ts +12 -1
  125. package/dist/types/components/datetime/datetime.d.ts +4 -4
  126. package/dist/types/components/fab-button/fab-button.d.ts +7 -1
  127. package/dist/types/components/input/input.d.ts +7 -10
  128. package/dist/types/components/input-password-toggle/input-password-toggle.d.ts +14 -2
  129. package/dist/types/components/item/item.d.ts +7 -1
  130. package/dist/types/components/menu-button/menu-button.d.ts +6 -1
  131. package/dist/types/components/refresher-content/refresher-content.d.ts +6 -0
  132. package/dist/types/components/reorder/reorder.d.ts +1 -1
  133. package/dist/types/components/select/select.d.ts +7 -12
  134. package/dist/types/components/select-modal/select-modal.d.ts +6 -1
  135. package/dist/types/components/textarea/textarea.d.ts +8 -8
  136. package/dist/types/components/toggle/toggle.d.ts +5 -2
  137. package/dist/types/utils/config.d.ts +22 -2
  138. package/dist/types/utils/test/playwright/field-geometry.d.ts +13 -0
  139. package/dist/types/utils/test/playwright/index.d.ts +1 -0
  140. package/hydrate/index.js +213 -375
  141. package/hydrate/index.mjs +213 -375
  142. package/package.json +1 -2
  143. package/components/p-B1AK6oR-.js +0 -4
  144. package/components/p-BDqjX7Z_.js +0 -4
  145. package/components/p-BYSs-jZz.js +0 -4
  146. package/components/p-CPdOdJa7.js +0 -4
  147. package/components/p-D1t981Ih.js +0 -4
  148. package/components/p-Dhi5xtNS.js +0 -4
  149. package/components/p-DzaPYdjZ.js +0 -4
  150. package/components/p-e73ca-aN.js +0 -4
  151. package/components/p-fIOYmaqA.js +0 -4
  152. package/dist/cjs/caret-down-vtVgfXIs.js +0 -8
  153. package/dist/cjs/caret-left-CxZXLRv5.js +0 -8
  154. package/dist/cjs/caret-right-CRCgv98E.js +0 -8
  155. package/dist/cjs/list-a-7GSA6K.js +0 -8
  156. package/dist/cjs/x-BTF99yFH.js +0 -8
  157. package/dist/esm/caret-down-D1t981Ih.js +0 -6
  158. package/dist/esm/caret-left-fIOYmaqA.js +0 -6
  159. package/dist/esm/caret-right-BYSs-jZz.js +0 -6
  160. package/dist/esm/list-Dhi5xtNS.js +0 -6
  161. package/dist/esm/x-BDqjX7Z_.js +0 -6
  162. package/dist/ionic/p-04d96dad.entry.js +0 -4
  163. package/dist/ionic/p-0a924e6a.entry.js +0 -4
  164. package/dist/ionic/p-2da21af7.entry.js +0 -4
  165. package/dist/ionic/p-2fc1521f.entry.js +0 -4
  166. package/dist/ionic/p-73309d35.entry.js +0 -4
  167. package/dist/ionic/p-74ab5f49.entry.js +0 -4
  168. package/dist/ionic/p-83d13d56.entry.js +0 -4
  169. package/dist/ionic/p-8dcd18cf.entry.js +0 -4
  170. package/dist/ionic/p-9ca59419.entry.js +0 -4
  171. package/dist/ionic/p-BDqjX7Z_.js +0 -4
  172. package/dist/ionic/p-BYSs-jZz.js +0 -4
  173. package/dist/ionic/p-D1t981Ih.js +0 -4
  174. package/dist/ionic/p-Dhi5xtNS.js +0 -4
  175. package/dist/ionic/p-b10dc124.entry.js +0 -4
  176. package/dist/ionic/p-b7ce4a50.entry.js +0 -4
  177. package/dist/ionic/p-c7035f3f.entry.js +0 -4
  178. package/dist/ionic/p-c91fc9d4.entry.js +0 -4
  179. package/dist/ionic/p-cd4791d5.entry.js +0 -4
  180. package/dist/ionic/p-e9480e7f.entry.js +0 -4
  181. package/dist/ionic/p-e9fdff49.entry.js +0 -4
  182. package/dist/ionic/p-f82b13a6.entry.js +0 -4
  183. package/dist/ionic/p-fIOYmaqA.js +0 -4
@@ -453,10 +453,6 @@
453
453
  overflow: hidden;
454
454
  }
455
455
 
456
- .textarea-outline {
457
- box-sizing: border-box;
458
- }
459
-
460
456
  /**
461
457
  * If no label text is placed into the slot
462
458
  * then the element should be hidden otherwise
@@ -562,32 +558,6 @@
562
558
  --border-color: var(--token-border-input-default, var(--token-primitives-neutral-600, #8c8c8c));
563
559
  }
564
560
 
565
- :host(.textarea-fill-outline) .textarea-wrapper {
566
- background: initial;
567
- }
568
-
569
- :host(.textarea-fill-outline) .textarea-wrapper-inner {
570
- /**
571
- * The border should be relative to the inner wrapper
572
- * so that it does not include the label.
573
- */
574
- position: relative;
575
- }
576
-
577
- :host(.textarea-fill-outline) .textarea-outline {
578
- right: 0;
579
- left: 0;
580
- top: 0;
581
- bottom: 0;
582
- border-radius: var(--border-radius);
583
- position: absolute;
584
- width: 100%;
585
- height: 100%;
586
- pointer-events: none;
587
- border: var(--border-width) var(--border-style) var(--border-color);
588
- background: var(--background);
589
- }
590
-
591
561
  /**
592
562
  * The bottom content should never have
593
563
  * a border with the outline style.
@@ -612,18 +582,6 @@
612
582
  --background: var(--token-bg-input-bold-default, var(--token-primitives-neutral-200, #eae9e9));
613
583
  }
614
584
 
615
- :host(.textarea-fill-solid) .textarea-wrapper {
616
- border-bottom: none;
617
- background: none;
618
- }
619
-
620
- :host(.textarea-fill-solid) .textarea-wrapper-inner {
621
- border-radius: var(--border-radius);
622
- position: relative;
623
- border: var(--border-width) var(--border-style) var(--border-color);
624
- background: var(--background);
625
- }
626
-
627
585
  :host(.textarea-fill-solid) .textarea-bottom {
628
586
  --border-width: var(--token-scale-0, 0px);
629
587
  }
@@ -674,21 +632,31 @@
674
632
  --highlight-color-focused: var(--ion-color-base);
675
633
  }
676
634
 
677
- :host(.textarea-size-small) .textarea-wrapper-inner {
635
+ /**
636
+ * The size padding is scoped to the field cells rather than to the wrapper, so
637
+ * the label, which sits outside the field box, does not inherit it.
638
+ */
639
+ :host(.textarea-size-small) .textarea-start,
640
+ :host(.textarea-size-small) .native-wrapper,
641
+ :host(.textarea-size-small) .textarea-end {
678
642
  --padding-top: var(--token-space-200, var(--token-scale-200, 8px));
679
643
  --padding-end: var(--token-space-300, var(--token-scale-300, 12px));
680
644
  --padding-bottom: var(--token-space-200, var(--token-scale-200, 8px));
681
645
  --padding-start: var(--token-space-300, var(--token-scale-300, 12px));
682
646
  }
683
647
 
684
- :host(.textarea-size-medium) .textarea-wrapper-inner {
648
+ :host(.textarea-size-medium) .textarea-start,
649
+ :host(.textarea-size-medium) .native-wrapper,
650
+ :host(.textarea-size-medium) .textarea-end {
685
651
  --padding-top: var(--token-space-300, var(--token-scale-300, 12px));
686
652
  --padding-end: var(--token-space-400, var(--token-scale-400, 16px));
687
653
  --padding-bottom: var(--token-space-300, var(--token-scale-300, 12px));
688
654
  --padding-start: var(--token-space-400, var(--token-scale-400, 16px));
689
655
  }
690
656
 
691
- :host(.textarea-size-large) .textarea-wrapper-inner {
657
+ :host(.textarea-size-large) .textarea-start,
658
+ :host(.textarea-size-large) .native-wrapper,
659
+ :host(.textarea-size-large) .textarea-end {
692
660
  --padding-top: var(--token-space-400, var(--token-scale-400, 16px));
693
661
  --padding-end: var(--token-space-500, var(--token-scale-500, 20px));
694
662
  --padding-bottom: var(--token-space-400, var(--token-scale-400, 16px));
@@ -714,63 +682,158 @@
714
682
  --border-radius: var(--token-rectangular-xl, var(--token-border-radius-0, var(--token-scale-0, 0px)));
715
683
  }
716
684
 
717
- :host([auto-grow]) .textarea-wrapper-inner {
718
- height: auto;
719
- }
720
-
721
685
  :host([auto-grow]) .native-wrapper {
722
686
  min-height: inherit;
723
687
  max-height: inherit;
724
688
  }
725
689
 
690
+ /**
691
+ * The label is nested in the control so that every theme renders the same
692
+ * tree. Laying the wrapper out as a grid and flattening the control lets the
693
+ * label sit outside the field box, which is drawn by `::before` over the cells
694
+ * the label does not occupy.
695
+ */
726
696
  .textarea-wrapper {
727
- gap: var(--token-space-100, var(--token-scale-100, 4px));
697
+ display: grid;
698
+ /**
699
+ * Auto rows stretch to fill any extra height by default, which would pad the
700
+ * label row and pull the field box away from it.
701
+ */
702
+ align-content: start;
703
+ /**
704
+ * The wrapper spans the label too, so the background belongs on the field
705
+ * box rather than here.
706
+ */
707
+ background: transparent;
728
708
  }
729
709
 
730
- :host(.textarea-label-placement-start) .textarea-wrapper {
731
- flex-direction: row;
710
+ .textarea-control {
711
+ display: contents;
712
+ }
713
+
714
+ .textarea-wrapper::before {
715
+ border-radius: var(--border-radius, 0);
716
+ align-self: stretch;
717
+ box-sizing: border-box;
718
+ content: "";
719
+ pointer-events: none;
720
+ /**
721
+ * The wrapper takes its min-height from the host and the pseudo-element
722
+ * inherits it, so a min-height on the host still reaches the box.
723
+ */
724
+ min-height: inherit;
725
+ border: var(--border-width) var(--border-style) var(--border-color);
726
+ background: var(--background);
727
+ }
728
+
729
+ :host(.textarea-label-placement-start) .textarea-wrapper,
730
+ :host(.textarea-label-placement-fixed) .textarea-wrapper {
731
+ grid-template-columns: [label] auto [field-start slot-start] auto [native] minmax(0, 1fr) [slot-end] auto [field-end];
732
+ grid-template-rows: [label field] auto;
733
+ }
734
+
735
+ :host(.textarea-label-placement-start) .label-text-wrapper,
736
+ :host(.textarea-label-placement-fixed) .label-text-wrapper {
737
+ -webkit-margin-end: var(--token-space-100, var(--token-scale-100, 4px));
738
+ margin-inline-end: var(--token-space-100, var(--token-scale-100, 4px));
739
+ grid-column: label;
732
740
  }
733
741
 
734
742
  :host(.textarea-label-placement-end) .textarea-wrapper {
735
- flex-direction: row-reverse;
743
+ grid-template-columns: [field-start slot-start] auto [native] minmax(0, 1fr) [slot-end] auto [field-end label] auto;
744
+ grid-template-rows: [label field] auto;
745
+ }
746
+
747
+ :host(.textarea-label-placement-end) .label-text-wrapper {
748
+ -webkit-margin-start: var(--token-space-100, var(--token-scale-100, 4px));
749
+ margin-inline-start: var(--token-space-100, var(--token-scale-100, 4px));
750
+ grid-column: label;
736
751
  }
737
752
 
738
753
  :host(.textarea-label-placement-stacked) .textarea-wrapper,
739
754
  :host(.textarea-label-placement-floating) .textarea-wrapper {
740
- flex-direction: column;
741
- align-items: start;
755
+ grid-template-columns: [field-start slot-start] auto [native] minmax(0, 1fr) [slot-end] auto [field-end];
756
+ grid-template-rows: [label] auto [field] auto;
757
+ }
758
+
759
+ :host(.textarea-label-placement-stacked) .label-text-wrapper,
760
+ :host(.textarea-label-placement-floating) .label-text-wrapper {
761
+ margin-bottom: var(--token-space-100, var(--token-scale-100, 4px));
762
+ grid-column: field-start/field-end;
763
+ }
764
+
765
+ /**
766
+ * The templates above all name the same lines, so every placement shares these.
767
+ * A label with its own row lands on the label row. Otherwise that name resolves
768
+ * to the only row there is.
769
+ */
770
+ .label-text-wrapper {
771
+ grid-row: label;
772
+ }
773
+
774
+ .textarea-start {
775
+ grid-row: field;
776
+ grid-column: slot-start;
742
777
  }
743
778
 
744
- .textarea-wrapper-inner {
779
+ .native-wrapper {
780
+ grid-row: field;
781
+ grid-column: native;
782
+ }
783
+
784
+ .textarea-end {
785
+ grid-row: field;
786
+ grid-column: slot-end;
787
+ }
788
+
789
+ .textarea-wrapper::before {
790
+ grid-row: field;
791
+ grid-column: field-start/field-end;
792
+ }
793
+
794
+ /**
795
+ * The padding sits on the cells that the field box covers, so the label is not
796
+ * indented by it.
797
+ */
798
+ .textarea-start {
745
799
  -webkit-padding-start: var(--padding-start);
746
800
  padding-inline-start: var(--padding-start);
747
- -webkit-padding-end: var(--padding-end);
748
- padding-inline-end: var(--padding-end);
749
801
  padding-top: var(--padding-top);
750
802
  padding-bottom: var(--padding-bottom);
751
- display: flex;
752
- width: 100%;
753
- min-height: inherit;
754
- box-sizing: border-box;
755
803
  }
756
804
 
757
- :host([rows]:not([auto-grow])) .textarea-wrapper-inner {
758
- -webkit-padding-start: var(--padding-start);
759
- padding-inline-start: var(--padding-start);
805
+ /**
806
+ * The native wrapper's own content sizes the field row, and it stretches to
807
+ * fill it. The shared stylesheet sets `height: 100%`, which would resolve
808
+ * against the row this element is itself sizing, so reset it here.
809
+ */
810
+ .native-wrapper {
811
+ padding-top: var(--padding-top);
812
+ padding-bottom: var(--padding-bottom);
813
+ align-self: stretch;
814
+ height: auto;
815
+ }
816
+
817
+ .textarea-end {
760
818
  -webkit-padding-end: var(--padding-end);
761
819
  padding-inline-end: var(--padding-end);
820
+ padding-top: var(--padding-top);
821
+ padding-bottom: var(--padding-bottom);
822
+ }
823
+
824
+ :host([rows]:not([auto-grow])) .native-wrapper {
762
825
  padding-top: 0;
763
826
  padding-bottom: 0;
764
827
  }
765
828
 
766
829
  /**
767
- * Top and bottom padding are applied to ensure that text remains visible
768
- * as it scrolls past the container boundaries. This provides a "sneak peek"
769
- * of the content transitioning off-screen, mirroring native textarea behavior
770
- * where text isn't immediately clipped or "glued" to the border at the
830
+ * Top and bottom padding are applied to ensure that text remains visible
831
+ * as it scrolls past the container boundaries. This provides a "sneak peek"
832
+ * of the content transitioning off-screen, mirroring native textarea behavior
833
+ * where text isn't immediately clipped or "glued" to the border at the
771
834
  * scroll limits.
772
835
  */
773
- :host([rows]:not([auto-grow])) .textarea-wrapper-inner .native-textarea {
836
+ :host([rows]:not([auto-grow])) .native-wrapper .native-textarea {
774
837
  padding-top: var(--padding-top);
775
838
  padding-bottom: var(--padding-bottom);
776
839
  }
@@ -881,21 +944,6 @@ ion-icon {
881
944
  margin-top: var(--token-space-050, var(--token-scale-050, 2px));
882
945
  }
883
946
 
884
- :host([rows]:not([auto-grow])) .textarea-start,
885
- :host([rows]:not([auto-grow])) .textarea-end {
886
- padding-left: 0;
887
- padding-right: 0;
888
- padding-top: var(--padding-top);
889
- padding-bottom: var(--padding-bottom);
890
- }
891
-
892
- .textarea-control {
893
- padding-left: 0;
894
- padding-right: 0;
895
- padding-top: 0;
896
- padding-bottom: 0;
897
- }
898
-
899
947
  :host(.has-focus) {
900
948
  --border-color: var(--token-border-focus-default, var(--token-primitives-blue-400, #b5c0f7));
901
949
  --border-width: var(--token-border-size-050, var(--token-scale-050, 2px));
@@ -455,10 +455,6 @@
455
455
  overflow: hidden;
456
456
  }
457
457
 
458
- .textarea-outline {
459
- box-sizing: border-box;
460
- }
461
-
462
458
  /**
463
459
  * If no label text is placed into the slot
464
460
  * then the element should be hidden otherwise
@@ -25,6 +25,9 @@ import { getCounterText } from "../input/input.utils";
25
25
  * @part error-text - Supporting text displayed beneath the textarea label when the textarea is invalid and touched.
26
26
  * @part counter - The character counter displayed when the counter property is set.
27
27
  * @part bottom - The container element for helper text, error text, and counter.
28
+ * @part start - The wrapper element for the content in the start slot.
29
+ * @part control - The wrapper element containing the label and the native textarea. The `"ionic"` theme flattens this wrapper, so it cannot be given a box of its own there.
30
+ * @part end - The wrapper element for the content in the end slot.
28
31
  */
29
32
  export class Textarea {
30
33
  constructor() {
@@ -528,32 +531,24 @@ export class Textarea {
528
531
  const { inputId, disabled } = this;
529
532
  return (h("textarea", { class: "native-textarea", part: "native", 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 }, this.getValue()));
530
533
  }
531
- /**
532
- * The ionic theme keeps the label above a field box that wraps the slots and
533
- * the textarea, so the label can size independently of the slotted content.
534
- */
535
534
  renderStartContainer() {
536
- return (h("div", { key: "start", class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { name: "start" })));
535
+ return (h("div", { key: "start", class: "textarea-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { name: "start" })));
537
536
  }
538
537
  renderEndContainer() {
539
- return (h("div", { key: "end", class: "textarea-end" }, h("slot", { name: "end" })));
540
- }
541
- renderIonicField() {
542
- return [
543
- this.renderLabel(),
544
- h("div", { key: "inner", class: "textarea-wrapper-inner" }, this.getFill() === 'outline' && h("div", { key: "outline", class: "textarea-outline" }), this.renderStartContainer(), h("div", { key: "control", class: "textarea-control" }, h("div", { class: "native-wrapper", ref: (el) => (this.textareaWrapper = el), part: "container" }, this.renderNativeTextarea())), this.renderEndContainer()),
545
- ];
538
+ return (h("div", { key: "end", class: "textarea-end", part: "end" }, h("slot", { name: "end" })));
546
539
  }
547
540
  /**
548
- * The ios and md themes nest the label alongside the textarea so a floating
549
- * label can escape the control and clear the start and end slots.
541
+ * Every theme renders the same label and control structure, aside from the md
542
+ * outline container below. The label is nested alongside the textarea so the
543
+ * ios and md floating label can escape the control and clear the start and
544
+ * end slots. The ionic theme lifts it outside the field with grid instead.
550
545
  */
551
- renderNativeField() {
546
+ renderField() {
552
547
  const hasOutlineFill = getIonTheme(this) === 'md' && this.getFill() === 'outline';
553
548
  return [
554
549
  hasOutlineFill && this.renderOutlineContainer(),
555
550
  this.renderStartContainer(),
556
- h("div", { key: "control", class: "textarea-control" }, this.renderLabel(), h("div", { class: "native-wrapper", ref: (el) => (this.textareaWrapper = el), part: "container" }, this.renderNativeTextarea())),
551
+ h("div", { key: "control", class: "textarea-control", part: "control" }, this.renderLabel(), h("div", { class: "native-wrapper", ref: (el) => (this.textareaWrapper = el), part: "container" }, this.renderNativeTextarea())),
557
552
  this.renderEndContainer(),
558
553
  ];
559
554
  }
@@ -616,7 +611,7 @@ export class Textarea {
616
611
  * the textarea has a value or is focused.
617
612
  */
618
613
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus));
619
- return (h(Host, { key: 'd577d18d209f4e9cee1c2addb78ab0c2e57effdf', class: createColorClasses(this.color, {
614
+ return (h(Host, { key: '696d649f4d05739857823887be65ae9252ba77dc', class: createColorClasses(this.color, {
620
615
  [theme]: true,
621
616
  'has-value': hasValue,
622
617
  'has-focus': hasFocus,
@@ -628,7 +623,7 @@ export class Textarea {
628
623
  'in-item': inItem,
629
624
  'textarea-disabled': disabled,
630
625
  'textarea-readonly': readonly,
631
- }) }, h("label", { key: '188cff345aec306354bd7a9fabc1411d607139e3', class: "textarea-wrapper", htmlFor: inputId, onMouseDown: this.onLabelPointerDown, onClick: this.onLabelClick, part: "wrapper" }, theme === 'ionic' ? this.renderIonicField() : this.renderNativeField(), shouldRenderHighlight && h("div", { key: '13ef56def9848498f77c3c388be4abe0ea2aa38c', class: "textarea-highlight" })), this.renderBottomContent()));
626
+ }) }, h("label", { key: '61e64a211779e73def2b5d55a19dfc2f76463968', class: "textarea-wrapper", htmlFor: inputId, onMouseDown: this.onLabelPointerDown, onClick: this.onLabelClick, part: "wrapper" }, this.renderField(), shouldRenderHighlight && h("div", { key: '4ada5405635e1155e6b750ed797fd1ab82e6b314', class: "textarea-highlight" })), this.renderBottomContent()));
632
627
  }
633
628
  static get is() { return "ion-textarea"; }
634
629
  static get encapsulation() { return "shadow"; }
@@ -455,10 +455,6 @@
455
455
  overflow: hidden;
456
456
  }
457
457
 
458
- .textarea-outline {
459
- box-sizing: border-box;
460
- }
461
-
462
458
  /**
463
459
  * If no label text is placed into the slot
464
460
  * then the element should be hidden otherwise
@@ -1,8 +1,6 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import checkRegular from "@phosphor-icons/core/assets/regular/check.svg";
5
- import minusRegular from "@phosphor-icons/core/assets/regular/minus.svg";
6
4
  import { Build, Host, forceUpdate, h } from "@stencil/core";
7
5
  import { checkInvalidState, createItemMultipleInputsObserver } from "../../utils/forms/index";
8
6
  import { renderHiddenInput, inheritAriaAttributes } from "../../utils/helpers";
@@ -249,22 +247,17 @@ export class Toggle {
249
247
  setFocus() {
250
248
  this.el.focus();
251
249
  }
250
+ /**
251
+ * Get the default icon to use for the checked icon based on the theme.
252
+ */
252
253
  get toggleDefaultCheckedIcon() {
253
- // Determine the theme and map to default icons
254
+ // Determine the theme and map to the default icon
254
255
  const theme = getIonTheme(this);
255
- const defaultIcons = {
256
- ios: removeOutline,
257
- ionic: checkRegular,
258
- md: checkmarkOutline,
259
- };
260
- // Get the default icon based on the theme, falling back to 'md' icon if necessary
261
- const defaultIcon = defaultIcons[theme] || defaultIcons.md;
262
- // Return the default icon
263
- return defaultIcon;
256
+ return theme === 'ios' ? removeOutline : checkmarkOutline;
264
257
  }
265
258
  /**
266
259
  * Get the icon to use for the checked icon.
267
- * Otherwise, use the icon set in the config.
260
+ * Use the icon set in the config.
268
261
  * If no icon is set in the config, use the default icon.
269
262
  */
270
263
  get toggleCheckedIcon() {
@@ -272,20 +265,13 @@ export class Toggle {
272
265
  }
273
266
  /**
274
267
  * Get the icon to use for the unchecked icon.
275
- * Otherwise, use the icon set in the config.
268
+ * Use the icon set in the config.
276
269
  * If no icon is set in the config, use the default icon.
277
270
  */
278
271
  get toggleUncheckedIcon() {
279
- // Determine the theme and map to default icons
272
+ // Determine the theme and map to the default icon
280
273
  const theme = getIonTheme(this);
281
- const defaultIcons = {
282
- ios: ellipseOutline,
283
- ionic: minusRegular,
284
- md: removeOutline,
285
- };
286
- // Get the default icon based on the theme, falling back to 'md' icon if necessary
287
- const defaultIcon = defaultIcons[theme] || defaultIcons.md;
288
- // Return the configured toggle unchecked icon or the default icon
274
+ const defaultIcon = theme === 'ios' ? ellipseOutline : removeOutline;
289
275
  return config.get('toggleUncheckedIcon', defaultIcon);
290
276
  }
291
277
  renderOnOffSwitchLabels(checked) {
@@ -354,7 +340,7 @@ export class Toggle {
354
340
  // The attributes are matched too because the class needs the item to render.
355
341
  const inClickableItem = hostContext('ion-item.ion-activatable, ion-item[button], ion-item[href]', el);
356
342
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
357
- return (h(Host, { key: 'f3252e2c92ba027748413620748d479b8c865805', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses(color, {
343
+ return (h(Host, { key: '030fcdcc6074a283de193a04b4c52a9dda2a89b3', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses(color, {
358
344
  [theme]: true,
359
345
  'in-item': inItem,
360
346
  // `ion-focusable` has to stay on because it is what makes the item focusable.
@@ -369,10 +355,10 @@ export class Toggle {
369
355
  [`toggle-label-placement-${labelPlacement}`]: true,
370
356
  [`toggle-${rtl}`]: true,
371
357
  'ion-activatable': isIonicTheme,
372
- }) }, h("label", { key: '29c8cb1ad33e2e5d982fa19f2ebcfa7b328f3be7', class: "toggle-wrapper", htmlFor: inputId }, h("input", { key: '8aefbd4f2be1d1cbe5350200aa0ff321370ff101', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required, ...inheritedAttributes }), h("div", { key: '4bbf765fed7eb016cbd560cf1bd8cd4e32a28f20', class: {
358
+ }) }, h("label", { key: '1d7d4f534661093ea5ea7c885ca7ba355983ff88', class: "toggle-wrapper", htmlFor: inputId }, h("input", { key: '43de25ac1c815db634915c302c934d0827b32b4c', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required, ...inheritedAttributes }), h("div", { key: '9ed1fd6cd5b912b25628a2e2c29f0d758e0a7a24', class: {
373
359
  'label-text-wrapper': true,
374
360
  'label-text-wrapper-hidden': !hasLabel,
375
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: 'fbfd9fef85b53219b52b0736a7aa510877d52c78' }), this.renderHintText()), h("div", { key: 'b84836acce007f477c2a9c6dfaace3856068b58e', class: "native-wrapper" }, this.renderToggleControl()))));
361
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '0b7834f1e125d8a9c0acc7577f344812ccaf52fe' }), this.renderHintText()), h("div", { key: '16bebe3d305cccbfcf4d85eb16ac3fe94ae2c59c', class: "native-wrapper" }, this.renderToggleControl()))));
376
362
  }
377
363
  static get is() { return "ion-toggle"; }
378
364
  static get encapsulation() { return "shadow"; }
@@ -0,0 +1,34 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ import { expect } from "@playwright/test";
5
+ /**
6
+ * These tracks are adjacent with no gap, so the edges are meant to be equal.
7
+ * Engines round fractional track origins differently, and a real ordering
8
+ * regression moves a cell by a whole track, so a pixel of slack is safe.
9
+ */
10
+ const EDGE_TOLERANCE = 1;
11
+ /**
12
+ * Asserts that a form field's start slot, control and end slot occupy one row,
13
+ * in inline order.
14
+ *
15
+ * The ionic theme lays these out as grid columns. An invalid column template is
16
+ * dropped silently, and the cells then auto-place into a row each, so checking
17
+ * that they share a row is what catches it.
18
+ *
19
+ * @param host The form field element.
20
+ * @param prefix The component's cell-class prefix: `input`, `select` or `textarea`.
21
+ */
22
+ export const expectFieldCellsShareARow = async (host, prefix) => {
23
+ const start = await host.locator(`.${prefix}-start`).boundingBox();
24
+ const native = await host.locator('.native-wrapper').boundingBox();
25
+ const end = await host.locator(`.${prefix}-end`).boundingBox();
26
+ expect(native).not.toBeNull();
27
+ for (const cell of [start, end]) {
28
+ expect(cell).not.toBeNull();
29
+ expect(cell.y).toBeLessThan(native.y + native.height);
30
+ expect(native.y).toBeLessThan(cell.y + cell.height);
31
+ }
32
+ expect(native.x).toBeGreaterThan(start.x + start.width - EDGE_TOLERANCE);
33
+ expect(end.x).toBeGreaterThan(native.x + native.width - EDGE_TOLERANCE);
34
+ };
@@ -11,3 +11,4 @@ export * from './matchers';
11
11
  export * from './viewports';
12
12
  export * from './generator';
13
13
  export * from './detach-reattach';
14
+ export * from './field-geometry';