voyager-ionic-core 8.7.6 → 8.7.11

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 (266) hide show
  1. package/components/button.js +3 -7
  2. package/components/checkbox.js +64 -13
  3. package/components/header.js +42 -4
  4. package/components/index2.js +74 -3
  5. package/components/ion-accordion.js +93 -14
  6. package/components/ion-datetime.js +35 -2
  7. package/components/ion-input.js +6 -13
  8. package/components/ion-select.js +59 -10
  9. package/components/ion-textarea.js +5 -12
  10. package/components/ion-toggle.js +63 -16
  11. package/components/radio-group.js +60 -7
  12. package/components/validity.js +17 -0
  13. package/dist/cjs/{index-CD5Rjp23.js → index-094mMFB-.js} +76 -5
  14. package/dist/cjs/index.cjs.js +3 -3
  15. package/dist/cjs/ion-accordion_2.cjs.entry.js +91 -13
  16. package/dist/cjs/ion-app_8.cjs.entry.js +43 -5
  17. package/dist/cjs/ion-button_2.cjs.entry.js +3 -7
  18. package/dist/cjs/ion-checkbox.cjs.entry.js +61 -12
  19. package/dist/cjs/ion-datetime_3.cjs.entry.js +35 -2
  20. package/dist/cjs/ion-input.cjs.entry.js +6 -13
  21. package/dist/cjs/ion-modal.cjs.entry.js +1 -1
  22. package/dist/cjs/ion-nav_2.cjs.entry.js +1 -1
  23. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  24. package/dist/cjs/ion-radio_2.cjs.entry.js +57 -6
  25. package/dist/cjs/ion-select_3.cjs.entry.js +56 -9
  26. package/dist/cjs/ion-textarea.cjs.entry.js +5 -12
  27. package/dist/cjs/ion-toggle.cjs.entry.js +59 -14
  28. package/dist/cjs/ionic.cjs.js +1 -1
  29. package/dist/cjs/{ios.transition-j9CclgEW.js → ios.transition-BOt_uW73.js} +1 -1
  30. package/dist/cjs/loader.cjs.js +1 -1
  31. package/dist/cjs/{md.transition-CwFyRSfv.js → md.transition-Dt968VXB.js} +1 -1
  32. package/dist/cjs/validity-BpS37YFM.js +19 -0
  33. package/dist/collection/components/accordion/accordion.js +93 -14
  34. package/dist/collection/components/button/button.js +3 -7
  35. package/dist/collection/components/checkbox/checkbox.js +68 -13
  36. package/dist/collection/components/datetime/datetime.js +35 -2
  37. package/dist/collection/components/header/header.ios.css +27 -1
  38. package/dist/collection/components/header/header.js +5 -4
  39. package/dist/collection/components/header/header.utils.js +37 -0
  40. package/dist/collection/components/input/input.js +6 -14
  41. package/dist/collection/components/radio-group/radio-group.js +64 -7
  42. package/dist/collection/components/select/select.js +60 -12
  43. package/dist/collection/components/textarea/textarea.js +5 -13
  44. package/dist/collection/components/toggle/toggle.js +63 -16
  45. package/dist/collection/utils/forms/index.js +1 -0
  46. package/dist/collection/utils/forms/validity.js +15 -0
  47. package/dist/collection/utils/test/playwright/page/utils/set-content.js +7 -0
  48. package/dist/collection/utils/test/playwright/page/utils/spy-on-event.js +32 -0
  49. package/dist/collection/utils/transition/index.js +74 -3
  50. package/dist/docs.json +1 -1
  51. package/dist/esm/{index-D6G2seR8.js → index-r2D9DEro.js} +76 -5
  52. package/dist/esm/index.js +3 -3
  53. package/dist/esm/ion-accordion_2.entry.js +91 -13
  54. package/dist/esm/ion-app_8.entry.js +43 -5
  55. package/dist/esm/ion-button_2.entry.js +3 -7
  56. package/dist/esm/ion-checkbox.entry.js +61 -12
  57. package/dist/esm/ion-datetime_3.entry.js +35 -2
  58. package/dist/esm/ion-input.entry.js +6 -13
  59. package/dist/esm/ion-modal.entry.js +1 -1
  60. package/dist/esm/ion-nav_2.entry.js +1 -1
  61. package/dist/esm/ion-popover.entry.js +1 -1
  62. package/dist/esm/ion-radio_2.entry.js +57 -6
  63. package/dist/esm/ion-select_3.entry.js +56 -9
  64. package/dist/esm/ion-textarea.entry.js +5 -12
  65. package/dist/esm/ion-toggle.entry.js +59 -14
  66. package/dist/esm/ionic.js +1 -1
  67. package/dist/esm/{ios.transition-Bpq9ixwv.js → ios.transition-BDzw0_Hm.js} +1 -1
  68. package/dist/esm/loader.js +1 -1
  69. package/dist/esm/{md.transition-zOA0oanq.js → md.transition-BzDYi3qq.js} +1 -1
  70. package/dist/esm/validity-DJztqcrH.js +17 -0
  71. package/dist/ionic/index.esm.js +1 -1
  72. package/dist/ionic/ionic.esm.js +1 -1
  73. package/dist/ionic/p-40c261a3.entry.js +4 -0
  74. package/dist/ionic/p-43ed1ef5.entry.js +4 -0
  75. package/dist/ionic/p-4e41ea20.entry.js +4 -0
  76. package/dist/ionic/{p-323421af.entry.js → p-5a39a99a.entry.js} +1 -1
  77. package/dist/ionic/p-5fb517e4.entry.js +4 -0
  78. package/dist/ionic/p-7380261c.entry.js +4 -0
  79. package/dist/ionic/{p-9a36e2e7.entry.js → p-95bddd49.entry.js} +1 -1
  80. package/dist/ionic/{p-DPhQmGJN.js → p-C7hRNDhM.js} +1 -1
  81. package/dist/ionic/p-DJztqcrH.js +4 -0
  82. package/dist/ionic/p-DUt5fQmA.js +4 -0
  83. package/dist/ionic/{p-9R1XyICs.js → p-DZRJwG4S.js} +1 -1
  84. package/dist/ionic/p-c19f63d0.entry.js +4 -0
  85. package/dist/ionic/p-cb93126d.entry.js +4 -0
  86. package/dist/ionic/p-d0a2a1ab.entry.js +4 -0
  87. package/dist/ionic/p-d1f54e28.entry.js +4 -0
  88. package/dist/ionic/p-d3014190.entry.js +4 -0
  89. package/dist/ionic/{p-de7b5fa3.entry.js → p-e16b69e1.entry.js} +1 -1
  90. package/dist/ionic/svg/checkbox-outline.svg +1 -0
  91. package/dist/ionic/svg/checkbox-sharp.svg +1 -0
  92. package/dist/ionic/svg/checkbox.svg +1 -0
  93. package/dist/ionic/svg/checkmark-circle-outline.svg +1 -0
  94. package/dist/ionic/svg/checkmark-circle-sharp.svg +1 -0
  95. package/dist/ionic/svg/checkmark-circle.svg +1 -0
  96. package/dist/ionic/svg/checkmark-done-circle-outline.svg +1 -0
  97. package/dist/ionic/svg/checkmark-done-circle-sharp.svg +1 -0
  98. package/dist/ionic/svg/checkmark-done-circle.svg +1 -0
  99. package/dist/ionic/svg/checkmark-done-outline.svg +1 -0
  100. package/dist/ionic/svg/checkmark-done-sharp.svg +1 -0
  101. package/dist/ionic/svg/checkmark-done.svg +1 -0
  102. package/dist/ionic/svg/checkmark-outline.svg +1 -0
  103. package/dist/ionic/svg/checkmark-sharp.svg +1 -0
  104. package/dist/ionic/svg/checkmark.svg +1 -0
  105. package/dist/ionic/svg/chevron-back-circle-outline.svg +1 -0
  106. package/dist/ionic/svg/chevron-back-circle-sharp.svg +1 -0
  107. package/dist/ionic/svg/chevron-back-circle.svg +1 -0
  108. package/dist/ionic/svg/chevron-back-outline.svg +1 -0
  109. package/dist/ionic/svg/chevron-back-sharp.svg +1 -0
  110. package/dist/ionic/svg/chevron-back.svg +1 -0
  111. package/dist/ionic/svg/chevron-collapse-outline.svg +1 -0
  112. package/dist/ionic/svg/chevron-collapse-sharp.svg +1 -0
  113. package/dist/ionic/svg/chevron-collapse.svg +1 -0
  114. package/dist/ionic/svg/chevron-down-circle-outline.svg +1 -0
  115. package/dist/ionic/svg/chevron-down-circle-sharp.svg +1 -0
  116. package/dist/ionic/svg/chevron-down-circle.svg +1 -0
  117. package/dist/ionic/svg/chevron-down-outline.svg +1 -0
  118. package/dist/ionic/svg/chevron-down-sharp.svg +1 -0
  119. package/dist/ionic/svg/chevron-down.svg +1 -0
  120. package/dist/ionic/svg/chevron-expand-outline.svg +1 -0
  121. package/dist/ionic/svg/chevron-expand-sharp.svg +1 -0
  122. package/dist/ionic/svg/chevron-expand.svg +1 -0
  123. package/dist/ionic/svg/chevron-forward-circle-outline.svg +1 -0
  124. package/dist/ionic/svg/chevron-forward-circle-sharp.svg +1 -0
  125. package/dist/ionic/svg/chevron-forward-circle.svg +1 -0
  126. package/dist/ionic/svg/chevron-forward-outline.svg +1 -0
  127. package/dist/ionic/svg/chevron-forward-sharp.svg +1 -0
  128. package/dist/ionic/svg/chevron-forward.svg +1 -0
  129. package/dist/ionic/svg/chevron-up-circle-outline.svg +1 -0
  130. package/dist/ionic/svg/chevron-up-circle-sharp.svg +1 -0
  131. package/dist/ionic/svg/chevron-up-circle.svg +1 -0
  132. package/dist/ionic/svg/chevron-up-outline.svg +1 -0
  133. package/dist/ionic/svg/chevron-up-sharp.svg +1 -0
  134. package/dist/ionic/svg/chevron-up.svg +1 -0
  135. package/dist/ionic/svg/clipboard-outline.svg +1 -0
  136. package/dist/ionic/svg/clipboard-sharp.svg +1 -0
  137. package/dist/ionic/svg/clipboard.svg +1 -0
  138. package/dist/ionic/svg/close-circle-outline.svg +1 -0
  139. package/dist/ionic/svg/close-circle-sharp.svg +1 -0
  140. package/dist/ionic/svg/close-circle.svg +1 -0
  141. package/dist/ionic/svg/close-outline.svg +1 -0
  142. package/dist/ionic/svg/close-sharp.svg +1 -0
  143. package/dist/ionic/svg/close.svg +1 -0
  144. package/dist/ionic/svg/cloud-circle-outline.svg +1 -0
  145. package/dist/ionic/svg/cloud-circle-sharp.svg +1 -0
  146. package/dist/ionic/svg/cloud-circle.svg +1 -0
  147. package/dist/ionic/svg/cloud-done-outline.svg +1 -0
  148. package/dist/ionic/svg/cloud-done-sharp.svg +1 -0
  149. package/dist/ionic/svg/cloud-done.svg +1 -0
  150. package/dist/ionic/svg/cloud-download-outline.svg +1 -0
  151. package/dist/ionic/svg/cloud-download-sharp.svg +1 -0
  152. package/dist/ionic/svg/cloud-download.svg +1 -0
  153. package/dist/ionic/svg/cloud-offline-outline.svg +1 -0
  154. package/dist/ionic/svg/cloud-offline-sharp.svg +1 -0
  155. package/dist/ionic/svg/cloud-offline.svg +1 -0
  156. package/dist/ionic/svg/cloud-outline.svg +1 -0
  157. package/dist/ionic/svg/cloud-sharp.svg +1 -0
  158. package/dist/ionic/svg/cloud-upload-outline.svg +1 -0
  159. package/dist/ionic/svg/cloud-upload-sharp.svg +1 -0
  160. package/dist/ionic/svg/cloud-upload.svg +1 -0
  161. package/dist/ionic/svg/cloud.svg +1 -0
  162. package/dist/ionic/svg/cloudy-night-outline.svg +1 -0
  163. package/dist/ionic/svg/cloudy-night-sharp.svg +1 -0
  164. package/dist/ionic/svg/cloudy-night.svg +1 -0
  165. package/dist/ionic/svg/cloudy-outline.svg +1 -0
  166. package/dist/ionic/svg/cloudy-sharp.svg +1 -0
  167. package/dist/ionic/svg/cloudy.svg +1 -0
  168. package/dist/ionic/svg/code-download-outline.svg +1 -0
  169. package/dist/ionic/svg/code-download-sharp.svg +1 -0
  170. package/dist/ionic/svg/code-download.svg +1 -0
  171. package/dist/ionic/svg/code-outline.svg +1 -0
  172. package/dist/ionic/svg/code-sharp.svg +1 -0
  173. package/dist/ionic/svg/code-slash-outline.svg +1 -0
  174. package/dist/ionic/svg/code-slash-sharp.svg +1 -0
  175. package/dist/ionic/svg/code-slash.svg +1 -0
  176. package/dist/ionic/svg/code-working-outline.svg +1 -0
  177. package/dist/ionic/svg/code-working-sharp.svg +1 -0
  178. package/dist/ionic/svg/code-working.svg +1 -0
  179. package/dist/ionic/svg/code.svg +1 -0
  180. package/dist/ionic/svg/cog-outline.svg +1 -0
  181. package/dist/ionic/svg/cog-sharp.svg +1 -0
  182. package/dist/ionic/svg/cog.svg +1 -0
  183. package/dist/ionic/svg/color-fill-outline.svg +1 -0
  184. package/dist/ionic/svg/color-fill-sharp.svg +1 -0
  185. package/dist/ionic/svg/color-fill.svg +1 -0
  186. package/dist/ionic/svg/color-filter-outline.svg +1 -0
  187. package/dist/ionic/svg/color-filter-sharp.svg +1 -0
  188. package/dist/ionic/svg/color-filter.svg +1 -0
  189. package/dist/ionic/svg/color-palette-outline.svg +1 -0
  190. package/dist/ionic/svg/color-palette-sharp.svg +1 -0
  191. package/dist/ionic/svg/color-palette.svg +1 -0
  192. package/dist/ionic/svg/color-wand-outline.svg +1 -0
  193. package/dist/ionic/svg/color-wand-sharp.svg +1 -0
  194. package/dist/ionic/svg/color-wand.svg +1 -0
  195. package/dist/ionic/svg/compass-outline.svg +1 -0
  196. package/dist/ionic/svg/compass-sharp.svg +1 -0
  197. package/dist/ionic/svg/compass.svg +1 -0
  198. package/dist/ionic/svg/construct-outline.svg +1 -0
  199. package/dist/ionic/svg/construct-sharp.svg +1 -0
  200. package/dist/ionic/svg/construct.svg +1 -0
  201. package/dist/ionic/svg/contract-outline.svg +1 -0
  202. package/dist/ionic/svg/contract-sharp.svg +1 -0
  203. package/dist/ionic/svg/contract.svg +1 -0
  204. package/dist/ionic/svg/contrast-outline.svg +1 -0
  205. package/dist/ionic/svg/contrast-sharp.svg +1 -0
  206. package/dist/ionic/svg/contrast.svg +1 -0
  207. package/dist/ionic/svg/copy-outline.svg +1 -0
  208. package/dist/ionic/svg/copy-sharp.svg +1 -0
  209. package/dist/ionic/svg/copy.svg +1 -0
  210. package/dist/ionic/svg/create-outline.svg +1 -0
  211. package/dist/ionic/svg/create-sharp.svg +1 -0
  212. package/dist/ionic/svg/create.svg +1 -0
  213. package/dist/ionic/svg/crop-outline.svg +1 -0
  214. package/dist/ionic/svg/crop-sharp.svg +1 -0
  215. package/dist/ionic/svg/crop.svg +1 -0
  216. package/dist/ionic/svg/cube-outline.svg +1 -0
  217. package/dist/ionic/svg/cube-sharp.svg +1 -0
  218. package/dist/ionic/svg/cube.svg +1 -0
  219. package/dist/ionic/svg/cut-outline.svg +1 -0
  220. package/dist/ionic/svg/cut-sharp.svg +1 -0
  221. package/dist/ionic/svg/cut.svg +1 -0
  222. package/dist/ionic/svg/desktop-outline.svg +1 -0
  223. package/dist/ionic/svg/desktop-sharp.svg +1 -0
  224. package/dist/ionic/svg/desktop.svg +1 -0
  225. package/dist/ionic/svg/diamond-outline.svg +1 -0
  226. package/dist/ionic/svg/diamond-sharp.svg +1 -0
  227. package/dist/ionic/svg/diamond.svg +1 -0
  228. package/dist/ionic/svg/dice-outline.svg +1 -0
  229. package/dist/ionic/svg/dice-sharp.svg +1 -0
  230. package/dist/ionic/svg/dice.svg +1 -0
  231. package/dist/ionic/svg/disc-outline.svg +1 -0
  232. package/dist/ionic/svg/disc-sharp.svg +1 -0
  233. package/dist/ionic/svg/disc.svg +1 -0
  234. package/dist/ionic/svg/document-attach-outline.svg +1 -0
  235. package/dist/ionic/svg/document-attach-sharp.svg +1 -0
  236. package/dist/ionic/svg/document-attach.svg +1 -0
  237. package/dist/ionic/svg/document-lock-outline.svg +1 -0
  238. package/dist/ionic/svg/document-lock-sharp.svg +1 -0
  239. package/dist/ionic/svg/document-lock.svg +1 -0
  240. package/dist/ionic/svg/document-outline.svg +1 -0
  241. package/dist/types/components/accordion/accordion.d.ts +18 -1
  242. package/dist/types/components/checkbox/checkbox.d.ts +9 -2
  243. package/dist/types/components/datetime/datetime.d.ts +10 -0
  244. package/dist/types/components/header/header.utils.d.ts +10 -0
  245. package/dist/types/components/input/input.d.ts +0 -4
  246. package/dist/types/components/radio-group/radio-group.d.ts +9 -1
  247. package/dist/types/components/select/select.d.ts +7 -1
  248. package/dist/types/components/textarea/textarea.d.ts +0 -4
  249. package/dist/types/components/toggle/toggle.d.ts +7 -2
  250. package/dist/types/utils/forms/index.d.ts +1 -0
  251. package/dist/types/utils/forms/validity.d.ts +10 -0
  252. package/dist/types/utils/transition/index.d.ts +9 -0
  253. package/hydrate/index.js +687 -413
  254. package/hydrate/index.mjs +687 -413
  255. package/package.json +4 -4
  256. package/dist/ionic/p-1c8a476d.entry.js +0 -4
  257. package/dist/ionic/p-3355a2ff.entry.js +0 -4
  258. package/dist/ionic/p-4efea47a.entry.js +0 -4
  259. package/dist/ionic/p-62e50f80.entry.js +0 -4
  260. package/dist/ionic/p-785026d7.entry.js +0 -4
  261. package/dist/ionic/p-78c74a3e.entry.js +0 -4
  262. package/dist/ionic/p-7bcfc421.entry.js +0 -4
  263. package/dist/ionic/p-83fc84e7.entry.js +0 -4
  264. package/dist/ionic/p-913a7c1e.entry.js +0 -4
  265. package/dist/ionic/p-CMhMiYSX.js +0 -4
  266. package/dist/ionic/p-c17c0a01.entry.js +0 -4
@@ -786,6 +786,28 @@ const Datetime = class {
786
786
  destroyKeyboardMO();
787
787
  }
788
788
  };
789
+ /**
790
+ * TODO(FW-6931): Remove this fallback upon solving the root cause
791
+ * Fallback to ensure the datetime becomes ready even if
792
+ * IntersectionObserver never reports it as intersecting.
793
+ *
794
+ * This is primarily used in environments where the observer
795
+ * might not fire as expected, such as when running under
796
+ * synthetic tests that stub IntersectionObserver.
797
+ */
798
+ this.ensureReadyIfVisible = () => {
799
+ if (this.el.classList.contains('datetime-ready')) {
800
+ return;
801
+ }
802
+ const rect = this.el.getBoundingClientRect();
803
+ if (rect.width === 0 || rect.height === 0) {
804
+ return;
805
+ }
806
+ this.initializeListeners();
807
+ index.writeTask(() => {
808
+ this.el.classList.add('datetime-ready');
809
+ });
810
+ };
789
811
  this.processValue = (value) => {
790
812
  const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
791
813
  const valueToProcess = hasValue ? data.parseDate(value) : this.defaultParts;
@@ -1103,6 +1125,17 @@ const Datetime = class {
1103
1125
  * triggering the `hiddenIO` observer below.
1104
1126
  */
1105
1127
  helpers.raf(() => visibleIO === null || visibleIO === void 0 ? void 0 : visibleIO.observe(intersectionTrackerRef));
1128
+ /**
1129
+ * TODO(FW-6931): Remove this fallback upon solving the root cause
1130
+ * Fallback: If IntersectionObserver never reports that the
1131
+ * datetime is visible but the host clearly has layout, ensure
1132
+ * we still initialize listeners and mark the component as ready.
1133
+ *
1134
+ * We schedule this after everything has had a chance to run.
1135
+ */
1136
+ setTimeout(() => {
1137
+ this.ensureReadyIfVisible();
1138
+ }, 100);
1106
1139
  /**
1107
1140
  * We need to clean up listeners when the datetime is hidden
1108
1141
  * in a popover/modal so that we can properly scroll containers
@@ -1858,7 +1891,7 @@ const Datetime = class {
1858
1891
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1859
1892
  const hasWheelVariant = hasDatePresentation && preferWheel;
1860
1893
  helpers.renderHiddenInput(true, el, name, data.formatValue(value), disabled);
1861
- return (index.h(index.Host, { key: '57492534800ea059a7c2bbd9f0059cc0b75ae8d2', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, theme.createColorClasses(color, {
1894
+ return (index.h(index.Host, { key: 'efdbc0922670a841bc667ceac392cdc1dedffd01', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, theme.createColorClasses(color, {
1862
1895
  [mode]: true,
1863
1896
  ['datetime-readonly']: readonly,
1864
1897
  ['datetime-disabled']: disabled,
@@ -1868,7 +1901,7 @@ const Datetime = class {
1868
1901
  [`datetime-size-${size}`]: true,
1869
1902
  [`datetime-prefer-wheel`]: hasWheelVariant,
1870
1903
  [`datetime-grid`]: isGridStyle,
1871
- })) }, index.h("div", { key: '97dac5e5195635ac0bc5fb472b9d09e5c3c6bbc3', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1904
+ })) }, index.h("div", { key: '3f8bb75fcb0baff55182ef3aa1b535eacc58d81f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1872
1905
  }
1873
1906
  get el() { return index.getElement(this); }
1874
1907
  static get watchers() { return {
@@ -5,6 +5,7 @@
5
5
 
6
6
  var index = require('./index-D6Wc6v08.js');
7
7
  var notchController = require('./notch-controller-Bzqhjm4f.js');
8
+ var validity = require('./validity-BpS37YFM.js');
8
9
  var helpers = require('./helpers-DrTqNghc.js');
9
10
  var input_utils = require('./input.utils-B_QROI2g.js');
10
11
  var theme = require('./theme-CeDs6Hcv.js');
@@ -232,14 +233,6 @@ const Input = class {
232
233
  componentWillLoad() {
233
234
  this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
234
235
  }
235
- /**
236
- * Checks if the input is in an invalid state based on Ionic validation classes
237
- */
238
- checkInvalidState() {
239
- const hasIonTouched = this.el.classList.contains('ion-touched');
240
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
241
- return hasIonTouched && hasIonInvalid;
242
- }
243
236
  connectedCallback() {
244
237
  const { el } = this;
245
238
  this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
@@ -247,7 +240,7 @@ const Input = class {
247
240
  // Watch for class changes to update validation state
248
241
  if (typeof MutationObserver !== 'undefined') {
249
242
  this.validationObserver = new MutationObserver(() => {
250
- const newIsInvalid = this.checkInvalidState();
243
+ const newIsInvalid = validity.checkInvalidState(el);
251
244
  if (this.isInvalid !== newIsInvalid) {
252
245
  this.isInvalid = newIsInvalid;
253
246
  // Force a re-render to update aria-describedby immediately
@@ -260,7 +253,7 @@ const Input = class {
260
253
  });
261
254
  }
262
255
  // Always set initial state
263
- this.isInvalid = this.checkInvalidState();
256
+ this.isInvalid = validity.checkInvalidState(el);
264
257
  this.debounceChanged();
265
258
  {
266
259
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -524,7 +517,7 @@ const Input = class {
524
517
  * TODO(FW-5592): Remove hasStartEndSlots condition
525
518
  */
526
519
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
527
- return (index.h(index.Host, { key: '8a51f0300d5bc66392f9ab9a6fa0b5d388072a33', class: theme.createColorClasses(this.color, {
520
+ return (index.h(index.Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: theme.createColorClasses(this.color, {
528
521
  [mode]: true,
529
522
  'has-value': hasValue,
530
523
  'has-focus': hasFocus,
@@ -535,14 +528,14 @@ const Input = class {
535
528
  'in-item': inItem,
536
529
  'in-item-color': theme.hostContext('ion-item.ion-color', this.el),
537
530
  'input-disabled': disabled,
538
- }) }, index.h("label", { key: '9f8cf88d7d0e27931b51bd9c67f048c7fc6f5703', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '7ad30bf9777774062a6ccf9a3ba804f251eef1bb', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: '8af0b0325d101df8eed7d24f2767d6ca4d307319', name: "start" }), index.h("input", Object.assign({ key: '1c53f7f9fa2567f3df19681cf4e7c21be382eae6', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, 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 }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (index.h("button", { key: 'b081d0e1ec1444b4c9cca145fc9cd2ad4a68b3da', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
531
+ }) }, index.h("label", { key: '353f68726ce180299bd9adc81e5ff7d26a48f54f', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '2034b4bad04fc157f3298a1805819216b6f439d0', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: '96bb5e30176b2bd76dfb75bfbf6c1c3d4403f4bb', name: "start" }), index.h("input", Object.assign({ key: '1a1d75b0e414a95c89d5a760757c33548d234aca', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, 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 }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (index.h("button", { key: '95f3df17b7691d9a2e7dcd4a51f16a94aa3ca36f', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
539
532
  /**
540
533
  * This prevents mobile browsers from
541
534
  * blurring the input when the clear
542
535
  * button is activated.
543
536
  */
544
537
  ev.preventDefault();
545
- }, onClick: this.clearTextInput }, index.h("ion-icon", { key: '01535299241c3635460c05646420acf62a1ff567', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: '480f3eb58b08ae792866a5b9b4c068748c5567cc', name: "end" })), shouldRenderHighlight && index.h("div", { key: 'a8609cacee88e4a09f1cca65b6a47cb79a56f35e', class: "input-highlight" })), this.renderBottomContent()));
538
+ }, onClick: this.clearTextInput }, index.h("ion-icon", { key: '16b0af75eed50c8115fb5597f73b5fbf71c2530e', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: 'c48da0f8ddb3764ac43efa705bb4a6bb2d9cc2fd', name: "end" })), shouldRenderHighlight && index.h("div", { key: 'f15238481fc20de56ca7ecb6e350b3c024cc755e', class: "input-highlight" })), this.renderBottomContent()));
546
539
  }
547
540
  get el() { return index.getElement(this); }
548
541
  static get watchers() { return {
@@ -11,7 +11,7 @@ var lockController = require('./lock-controller-aDB9wrEf.js');
11
11
  var capacitor = require('./capacitor-DmA66EwP.js');
12
12
  var overlays = require('./overlays-DxIZwUXI.js');
13
13
  var theme = require('./theme-CeDs6Hcv.js');
14
- var index$4 = require('./index-CD5Rjp23.js');
14
+ var index$4 = require('./index-094mMFB-.js');
15
15
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
16
16
  var keyboard = require('./keyboard-hHzlEQpk.js');
17
17
  var animation = require('./animation-Bt3H9L1C.js');
@@ -6,7 +6,7 @@
6
6
  var index = require('./index-D6Wc6v08.js');
7
7
  var cubicBezier = require('./cubic-bezier-DAjy1V-e.js');
8
8
  var helpers = require('./helpers-DrTqNghc.js');
9
- var index$1 = require('./index-CD5Rjp23.js');
9
+ var index$1 = require('./index-094mMFB-.js');
10
10
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
11
11
  var frameworkDelegate = require('./framework-delegate-DMJRBuDi.js');
12
12
 
@@ -10,7 +10,7 @@ var helpers = require('./helpers-DrTqNghc.js');
10
10
  var lockController = require('./lock-controller-aDB9wrEf.js');
11
11
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
12
12
  var theme = require('./theme-CeDs6Hcv.js');
13
- var index$1 = require('./index-CD5Rjp23.js');
13
+ var index$1 = require('./index-094mMFB-.js');
14
14
  var animation = require('./animation-Bt3H9L1C.js');
15
15
  require('./index-DkNv4J_i.js');
16
16
  require('./hardware-back-button-VCK4V3mG.js');
@@ -8,6 +8,7 @@ var helpers = require('./helpers-DrTqNghc.js');
8
8
  var compareWithUtils = require('./compare-with-utils-DSicavqM.js');
9
9
  var theme = require('./theme-CeDs6Hcv.js');
10
10
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
11
+ var validity = require('./validity-BpS37YFM.js');
11
12
 
12
13
  const radioIosCss = ":host{--inner-border-radius:50%;display:inline-block;position:relative;max-width:100%;min-height:inherit;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:2;-webkit-box-sizing:border-box;box-sizing:border-box}:host(.radio-disabled){pointer-events:none}.radio-icon{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:100%;height:100%;contain:layout size style}.radio-icon,.radio-inner{-webkit-box-sizing:border-box;box-sizing:border-box}input{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;margin:0;padding:0;border:0;outline:0;clip:rect(0 0 0 0);opacity:0;overflow:hidden;-webkit-appearance:none;-moz-appearance:none}:host(:focus){outline:none}:host(.in-item){-ms-flex:1 1 0px;flex:1 1 0;width:100%;height:100%}:host([slot=start]),:host([slot=end]){-ms-flex:initial;flex:initial;width:auto}.radio-wrapper{display:-ms-flexbox;display:flex;position:relative;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;height:inherit;min-height:inherit;cursor:inherit}.label-text-wrapper{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}:host(.in-item) .label-text-wrapper{margin-top:10px;margin-bottom:10px}:host(.in-item.radio-label-placement-stacked) .label-text-wrapper{margin-top:10px;margin-bottom:16px}:host(.in-item.radio-label-placement-stacked) .native-wrapper{margin-bottom:10px}.label-text-wrapper-hidden{display:none}.native-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}:host(.radio-justify-space-between) .radio-wrapper{-ms-flex-pack:justify;justify-content:space-between}:host(.radio-justify-start) .radio-wrapper{-ms-flex-pack:start;justify-content:start}:host(.radio-justify-end) .radio-wrapper{-ms-flex-pack:end;justify-content:end}:host(.radio-alignment-start) .radio-wrapper{-ms-flex-align:start;align-items:start}:host(.radio-alignment-center) .radio-wrapper{-ms-flex-align:center;align-items:center}:host(.radio-justify-space-between),:host(.radio-justify-start),:host(.radio-justify-end),:host(.radio-alignment-start),:host(.radio-alignment-center){display:block}:host(.radio-label-placement-start) .radio-wrapper{-ms-flex-direction:row;flex-direction:row}:host(.radio-label-placement-start) .label-text-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:16px;margin-inline-end:16px}:host(.radio-label-placement-end) .radio-wrapper{-ms-flex-direction:row-reverse;flex-direction:row-reverse}:host(.radio-label-placement-end) .label-text-wrapper{-webkit-margin-start:16px;margin-inline-start:16px;-webkit-margin-end:0;margin-inline-end:0}:host(.radio-label-placement-fixed) .label-text-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:16px;margin-inline-end:16px}:host(.radio-label-placement-fixed) .label-text-wrapper{-ms-flex:0 0 100px;flex:0 0 100px;width:100px;min-width:100px}:host(.radio-label-placement-stacked) .radio-wrapper{-ms-flex-direction:column;flex-direction:column}:host(.radio-label-placement-stacked) .label-text-wrapper{-webkit-transform:scale(0.75);transform:scale(0.75);margin-left:0;margin-right:0;margin-bottom:16px;max-width:calc(100% / 0.75)}:host(.radio-label-placement-stacked.radio-alignment-start) .label-text-wrapper{-webkit-transform-origin:left top;transform-origin:left top}:host-context([dir=rtl]):host(.radio-label-placement-stacked.radio-alignment-start) .label-text-wrapper,:host-context([dir=rtl]).radio-label-placement-stacked.radio-alignment-start .label-text-wrapper{-webkit-transform-origin:right top;transform-origin:right top}@supports selector(:dir(rtl)){:host(.radio-label-placement-stacked.radio-alignment-start:dir(rtl)) .label-text-wrapper{-webkit-transform-origin:right top;transform-origin:right top}}:host(.radio-label-placement-stacked.radio-alignment-center) .label-text-wrapper{-webkit-transform-origin:center top;transform-origin:center top}:host-context([dir=rtl]):host(.radio-label-placement-stacked.radio-alignment-center) .label-text-wrapper,:host-context([dir=rtl]).radio-label-placement-stacked.radio-alignment-center .label-text-wrapper{-webkit-transform-origin:calc(100% - center) top;transform-origin:calc(100% - center) top}@supports selector(:dir(rtl)){:host(.radio-label-placement-stacked.radio-alignment-center:dir(rtl)) .label-text-wrapper{-webkit-transform-origin:calc(100% - center) top;transform-origin:calc(100% - center) top}}:host{--color-checked:var(--ion-color-primary, #0054e9)}:host(.ion-color.radio-checked) .radio-inner{border-color:var(--ion-color-base)}.item-radio.item-ios ion-label{-webkit-margin-start:0;margin-inline-start:0}.radio-inner{width:33%;height:50%}:host(.radio-checked) .radio-inner{-webkit-transform:rotate(45deg);transform:rotate(45deg);border-width:0.125rem;border-top-width:0;border-left-width:0;border-style:solid;border-color:var(--color-checked)}:host(.radio-disabled){opacity:0.3}:host(.ion-focused) .radio-icon::after{border-radius:var(--inner-border-radius);top:-8px;display:block;position:absolute;width:36px;height:36px;background:var(--ion-color-primary-tint, #1a65eb);content:\"\";opacity:0.2}:host(.ion-focused) .radio-icon::after{inset-inline-start:-9px}.native-wrapper .radio-icon{width:0.9375rem;height:1.5rem}";
13
14
 
@@ -177,6 +178,10 @@ const RadioGroup = class {
177
178
  this.helperTextId = `${this.inputId}-helper-text`;
178
179
  this.errorTextId = `${this.inputId}-error-text`;
179
180
  this.labelId = `${this.inputId}-lbl`;
181
+ /**
182
+ * Track validation state for proper aria-live announcements.
183
+ */
184
+ this.isInvalid = false;
180
185
  /**
181
186
  * If `true`, the radios can be deselected.
182
187
  */
@@ -258,6 +263,52 @@ const RadioGroup = class {
258
263
  this.labelId = label.id = this.name + '-lbl';
259
264
  }
260
265
  }
266
+ // Watch for class changes to update validation state.
267
+ if (typeof MutationObserver !== 'undefined') {
268
+ this.validationObserver = new MutationObserver(() => {
269
+ const newIsInvalid = validity.checkInvalidState(this.el);
270
+ if (this.isInvalid !== newIsInvalid) {
271
+ this.isInvalid = newIsInvalid;
272
+ /**
273
+ * Screen readers tend to announce changes
274
+ * to `aria-describedby` when the attribute
275
+ * is changed during a blur event for a
276
+ * native form control.
277
+ * However, the announcement can be spotty
278
+ * when using a non-native form control
279
+ * and `forceUpdate()`.
280
+ * This is due to `forceUpdate()` internally
281
+ * rescheduling the DOM update to a lower
282
+ * priority queue regardless if it's called
283
+ * inside a Promise or not, thus causing
284
+ * the screen reader to potentially miss the
285
+ * change.
286
+ * By using a State variable inside a Promise,
287
+ * it guarantees a re-render immediately at
288
+ * a higher priority.
289
+ */
290
+ Promise.resolve().then(() => {
291
+ this.hintTextId = this.getHintTextId();
292
+ });
293
+ }
294
+ });
295
+ this.validationObserver.observe(this.el, {
296
+ attributes: true,
297
+ attributeFilter: ['class'],
298
+ });
299
+ }
300
+ // Always set initial state
301
+ this.isInvalid = validity.checkInvalidState(this.el);
302
+ }
303
+ componentWillLoad() {
304
+ this.hintTextId = this.getHintTextId();
305
+ }
306
+ disconnectedCallback() {
307
+ // Clean up validation observer to prevent memory leaks.
308
+ if (this.validationObserver) {
309
+ this.validationObserver.disconnect();
310
+ this.validationObserver = undefined;
311
+ }
261
312
  }
262
313
  getRadios() {
263
314
  return Array.from(this.el.querySelectorAll('ion-radio'));
@@ -333,16 +384,16 @@ const RadioGroup = class {
333
384
  * Renders the helper text or error text values
334
385
  */
335
386
  renderHintText() {
336
- const { helperText, errorText, helperTextId, errorTextId } = this;
387
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
337
388
  const hasHintText = !!helperText || !!errorText;
338
389
  if (!hasHintText) {
339
390
  return;
340
391
  }
341
- return (index.h("div", { class: "radio-group-top" }, index.h("div", { id: helperTextId, class: "helper-text" }, helperText), index.h("div", { id: errorTextId, class: "error-text" }, errorText)));
392
+ return (index.h("div", { class: "radio-group-top" }, index.h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), index.h("div", { id: errorTextId, class: "error-text", role: "alert" }, isInvalid ? errorText : null)));
342
393
  }
343
- getHintTextID() {
344
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
345
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
394
+ getHintTextId() {
395
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
396
+ if (isInvalid && errorText) {
346
397
  return errorTextId;
347
398
  }
348
399
  if (helperText) {
@@ -354,7 +405,7 @@ const RadioGroup = class {
354
405
  const { label, labelId, el, name, value } = this;
355
406
  const mode = ionicGlobal.getIonMode(this);
356
407
  helpers.renderHiddenInput(true, el, name, value, false);
357
- return (index.h(index.Host, { key: '81b8ebc96b2f383c36717f290d2959cc921ad6e8', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, onClick: this.onClick, class: mode }, this.renderHintText(), index.h("div", { key: '45b09efc10776b889a8f372cba80d25a3fc849da', class: "radio-group-wrapper" }, index.h("slot", { key: '58714934542c2fdd7396de160364f3f06b32e8f8' }))));
408
+ return (index.h(index.Host, { key: 'db593b3ed511e9395e3c7bfd91b787328692cd6d', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), index.h("div", { key: '85045b45a0100a45f3b9a35d1c5a25ec63d525c4', class: "radio-group-wrapper" }, index.h("slot", { key: '53dacb87ce62398e78771fb2efaf839ab922d946' }))));
358
409
  }
359
410
  get el() { return index.getElement(this); }
360
411
  static get watchers() { return {
@@ -6,6 +6,7 @@
6
6
  var index = require('./index-D6Wc6v08.js');
7
7
  var notchController = require('./notch-controller-Bzqhjm4f.js');
8
8
  var compareWithUtils = require('./compare-with-utils-DSicavqM.js');
9
+ var validity = require('./validity-BpS37YFM.js');
9
10
  var helpers = require('./helpers-DrTqNghc.js');
10
11
  var overlays = require('./overlays-DxIZwUXI.js');
11
12
  var dir = require('./dir-Cn0z1rJH.js');
@@ -45,6 +46,10 @@ const Select = class {
45
46
  * is applied in both cases.
46
47
  */
47
48
  this.hasFocus = false;
49
+ /**
50
+ * Track validation state for proper aria-live announcements.
51
+ */
52
+ this.isInvalid = false;
48
53
  /**
49
54
  * The text to display on the cancel button.
50
55
  */
@@ -174,9 +179,46 @@ const Select = class {
174
179
  */
175
180
  index.forceUpdate(this);
176
181
  });
182
+ // Watch for class changes to update validation state.
183
+ if (typeof MutationObserver !== 'undefined') {
184
+ this.validationObserver = new MutationObserver(() => {
185
+ const newIsInvalid = validity.checkInvalidState(this.el);
186
+ if (this.isInvalid !== newIsInvalid) {
187
+ this.isInvalid = newIsInvalid;
188
+ /**
189
+ * Screen readers tend to announce changes
190
+ * to `aria-describedby` when the attribute
191
+ * is changed during a blur event for a
192
+ * native form control.
193
+ * However, the announcement can be spotty
194
+ * when using a non-native form control
195
+ * and `forceUpdate()`.
196
+ * This is due to `forceUpdate()` internally
197
+ * rescheduling the DOM update to a lower
198
+ * priority queue regardless if it's called
199
+ * inside a Promise or not, thus causing
200
+ * the screen reader to potentially miss the
201
+ * change.
202
+ * By using a State variable inside a Promise,
203
+ * it guarantees a re-render immediately at
204
+ * a higher priority.
205
+ */
206
+ Promise.resolve().then(() => {
207
+ this.hintTextId = this.getHintTextId();
208
+ });
209
+ }
210
+ });
211
+ this.validationObserver.observe(el, {
212
+ attributes: true,
213
+ attributeFilter: ['class'],
214
+ });
215
+ }
216
+ // Always set initial state
217
+ this.isInvalid = validity.checkInvalidState(this.el);
177
218
  }
178
219
  componentWillLoad() {
179
220
  this.inheritedAttributes = helpers.inheritAttributes(this.el, ['aria-label']);
221
+ this.hintTextId = this.getHintTextId();
180
222
  }
181
223
  componentDidLoad() {
182
224
  /**
@@ -200,6 +242,11 @@ const Select = class {
200
242
  this.notchController.destroy();
201
243
  this.notchController = undefined;
202
244
  }
245
+ // Clean up validation observer to prevent memory leaks.
246
+ if (this.validationObserver) {
247
+ this.validationObserver.disconnect();
248
+ this.validationObserver = undefined;
249
+ }
203
250
  }
204
251
  /**
205
252
  * Open the select overlay. The overlay is either an alert, action sheet, or popover,
@@ -670,11 +717,11 @@ const Select = class {
670
717
  }
671
718
  renderListbox() {
672
719
  const { disabled, inputId, isExpanded, required } = this;
673
- return (index.h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
720
+ return (index.h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
674
721
  }
675
- getHintTextID() {
676
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
677
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
722
+ getHintTextId() {
723
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
724
+ if (isInvalid && errorText) {
678
725
  return errorTextId;
679
726
  }
680
727
  if (helperText) {
@@ -686,10 +733,10 @@ const Select = class {
686
733
  * Renders the helper text or error text values
687
734
  */
688
735
  renderHintText() {
689
- const { helperText, errorText, helperTextId, errorTextId } = this;
736
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
690
737
  return [
691
- index.h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText),
692
- index.h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText),
738
+ index.h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null),
739
+ index.h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null),
693
740
  ];
694
741
  }
695
742
  /**
@@ -737,7 +784,7 @@ const Select = class {
737
784
  * TODO(FW-5592): Remove hasStartEndSlots condition
738
785
  */
739
786
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
740
- return (index.h(index.Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: theme.createColorClasses(this.color, {
787
+ return (index.h(index.Host, { key: '35b5e18e6f79a802ff2d46d1242e80ff755cc0b9', onClick: this.onClick, class: theme.createColorClasses(this.color, {
741
788
  [mode]: true,
742
789
  'in-item': inItem,
743
790
  'in-item-color': theme.hostContext('ion-item.ion-color', el),
@@ -755,7 +802,7 @@ const Select = class {
755
802
  [`select-justify-${justify}`]: justifyEnabled,
756
803
  [`select-shape-${shape}`]: shape !== undefined,
757
804
  [`select-label-placement-${labelPlacement}`]: true,
758
- }) }, index.h("label", { key: '0d0c8ec55269adcac625f2899a547f4e7f3e3741', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'f6dfc93c0e23cbe75a2947abde67d842db2dad78', class: "select-wrapper-inner" }, index.h("slot", { key: '957bfadf9f101f519091419a362d3abdc2be66f6', name: "start" }), index.h("div", { key: 'ca349202a484e7f2e884533fd330f0b136754f7d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), index.h("slot", { key: 'f0e62a6533ff1c8f62bd2d27f60b23385c4fa9ed', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && index.h("div", { key: 'fb840d46bafafb09898ebeebbe8c181906a3d8a2', class: "select-highlight" })), this.renderBottomContent()));
805
+ }) }, index.h("label", { key: '6005b34a0c50bc4d7653a4276bc232ecd02e083c', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'c7e07aa81ae856c057f16275dd058f37c5670a47', class: "select-wrapper-inner" }, index.h("slot", { key: '7fc2deefe0424404caacdbbd9e08ed43ba55d28a', name: "start" }), index.h("div", { key: '157d74ee717b1bc30b5f1c233a09b0c8456aa68e', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), index.h("slot", { key: 'ea66db304528b82bf9317730b6dce3db2612f235', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && index.h("div", { key: '786eb1530b7476f0615d4e7c0bf4e7e4dc66509c', class: "select-highlight" })), this.renderBottomContent()));
759
806
  }
760
807
  get el() { return index.getElement(this); }
761
808
  static get watchers() { return {
@@ -5,6 +5,7 @@
5
5
 
6
6
  var index = require('./index-D6Wc6v08.js');
7
7
  var notchController = require('./notch-controller-Bzqhjm4f.js');
8
+ var validity = require('./validity-BpS37YFM.js');
8
9
  var helpers = require('./helpers-DrTqNghc.js');
9
10
  var input_utils = require('./input.utils-B_QROI2g.js');
10
11
  var theme = require('./theme-CeDs6Hcv.js');
@@ -192,14 +193,6 @@ const Textarea = class {
192
193
  this.el.click();
193
194
  }
194
195
  }
195
- /**
196
- * Checks if the textarea is in an invalid state based on Ionic validation classes
197
- */
198
- checkValidationState() {
199
- const hasIonTouched = this.el.classList.contains('ion-touched');
200
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
201
- return hasIonTouched && hasIonInvalid;
202
- }
203
196
  connectedCallback() {
204
197
  const { el } = this;
205
198
  this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
@@ -207,7 +200,7 @@ const Textarea = class {
207
200
  // Watch for class changes to update validation state
208
201
  if (typeof MutationObserver !== 'undefined') {
209
202
  this.validationObserver = new MutationObserver(() => {
210
- const newIsInvalid = this.checkValidationState();
203
+ const newIsInvalid = validity.checkInvalidState(this.el);
211
204
  if (this.isInvalid !== newIsInvalid) {
212
205
  this.isInvalid = newIsInvalid;
213
206
  // Force a re-render to update aria-describedby immediately
@@ -220,7 +213,7 @@ const Textarea = class {
220
213
  });
221
214
  }
222
215
  // Always set initial state
223
- this.isInvalid = this.checkValidationState();
216
+ this.isInvalid = validity.checkInvalidState(this.el);
224
217
  this.debounceChanged();
225
218
  {
226
219
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -484,7 +477,7 @@ const Textarea = class {
484
477
  * TODO(FW-5592): Remove hasStartEndSlots condition
485
478
  */
486
479
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
487
- return (index.h(index.Host, { key: '26b46666a92b3f652775bb1c46661f9a30392104', class: theme.createColorClasses(this.color, {
480
+ return (index.h(index.Host, { key: 'a70a62d7aae3831a50acd74f60b930925ada1326', class: theme.createColorClasses(this.color, {
488
481
  [mode]: true,
489
482
  'has-value': hasValue,
490
483
  'has-focus': hasFocus,
@@ -493,7 +486,7 @@ const Textarea = class {
493
486
  [`textarea-shape-${shape}`]: shape !== undefined,
494
487
  [`textarea-label-placement-${labelPlacement}`]: true,
495
488
  'textarea-disabled': disabled,
496
- }) }, index.h("label", { key: '2649da816216959ebe1f34cafd9dedbac20ec3c2', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'dca98593efece1b044dbcda045fa70882d715cb2', class: "textarea-wrapper-inner" }, index.h("div", { key: '2019daf87fddca5ec0b2e336f0376fd9642bae1b', class: "start-slot-wrapper" }, index.h("slot", { key: '36c423c394a71d08261705b9d6729e756bf65924', name: "start" })), index.h("div", { key: '0c3ea34105c7eddfa4094371c5d288c50ed10db3', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, index.h("textarea", Object.assign({ key: 'ce173b83b16aff43d293fa1edef9b66c6676227b', 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)), index.h("div", { key: '756e343cfd208bb5ad9ecf08d77cbb0a9606dc7b', class: "end-slot-wrapper" }, index.h("slot", { key: '0eb596814a037fa4634ff8c5bac0045540edfe21', name: "end" }))), shouldRenderHighlight && index.h("div", { key: 'df62f896eb6e0e2d1217aa487c198eb82a52bcb8', class: "textarea-highlight" })), this.renderBottomContent()));
489
+ }) }, index.h("label", { key: '8a2dd59a60f7469df84018eb0ede3a9ec3862703', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '1bfc368236e3da7a225a45118c27fbfc1fe5fa46', class: "textarea-wrapper-inner" }, index.h("div", { key: '215cbb2635ff52e31a8973376989b85e7245d40f', class: "start-slot-wrapper" }, index.h("slot", { key: '9f6b461cdee9d629deb695d2bea054ece2f32305', name: "start" })), index.h("div", { key: 'c1af35a2d5bc452bebe0b22a26d15ff52b4e9fc8', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, index.h("textarea", Object.assign({ key: '69a69b3cf0932baafbe37e6e846f1a571608d3f2', 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)), index.h("div", { key: 'c053ea8b865d0e29763aed2e4939cc9c9e374c15', class: "end-slot-wrapper" }, index.h("slot", { key: '930aa641833b0df54b9ea10368fc2f46d5f491f6', name: "end" }))), shouldRenderHighlight && index.h("div", { key: '8d12597d15f5f429d80e8272ea99e64ed924e482', class: "textarea-highlight" })), this.renderBottomContent()));
497
490
  }
498
491
  get el() { return index.getElement(this); }
499
492
  static get watchers() { return {
@@ -5,6 +5,7 @@
5
5
 
6
6
  var index = require('./index-D6Wc6v08.js');
7
7
  var helpers = require('./helpers-DrTqNghc.js');
8
+ var validity = require('./validity-BpS37YFM.js');
8
9
  var haptic = require('./haptic-ClPPQ_PS.js');
9
10
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
10
11
  var dir = require('./dir-Cn0z1rJH.js');
@@ -31,6 +32,10 @@ const Toggle = class {
31
32
  this.inheritedAttributes = {};
32
33
  this.didLoad = false;
33
34
  this.activated = false;
35
+ /**
36
+ * Track validation state for proper aria-live announcements.
37
+ */
38
+ this.isInvalid = false;
34
39
  /**
35
40
  * The name of the control, which is submitted with the form data.
36
41
  */
@@ -138,22 +143,58 @@ const Toggle = class {
138
143
  const { checked, value } = this;
139
144
  const isNowChecked = !checked;
140
145
  this.checked = isNowChecked;
141
- this.setFocus();
142
146
  this.ionChange.emit({
143
147
  checked: isNowChecked,
144
148
  value,
145
149
  });
146
150
  }
147
151
  async connectedCallback() {
152
+ const { didLoad, el } = this;
148
153
  /**
149
154
  * If we have not yet rendered
150
155
  * ion-toggle, then toggleTrack is not defined.
151
156
  * But if we are moving ion-toggle via appendChild,
152
157
  * then toggleTrack will be defined.
153
158
  */
154
- if (this.didLoad) {
159
+ if (didLoad) {
155
160
  this.setupGesture();
156
161
  }
162
+ // Watch for class changes to update validation state.
163
+ if (typeof MutationObserver !== 'undefined') {
164
+ this.validationObserver = new MutationObserver(() => {
165
+ const newIsInvalid = validity.checkInvalidState(el);
166
+ if (this.isInvalid !== newIsInvalid) {
167
+ this.isInvalid = newIsInvalid;
168
+ /**
169
+ * Screen readers tend to announce changes
170
+ * to `aria-describedby` when the attribute
171
+ * is changed during a blur event for a
172
+ * native form control.
173
+ * However, the announcement can be spotty
174
+ * when using a non-native form control
175
+ * and `forceUpdate()`.
176
+ * This is due to `forceUpdate()` internally
177
+ * rescheduling the DOM update to a lower
178
+ * priority queue regardless if it's called
179
+ * inside a Promise or not, thus causing
180
+ * the screen reader to potentially miss the
181
+ * change.
182
+ * By using a State variable inside a Promise,
183
+ * it guarantees a re-render immediately at
184
+ * a higher priority.
185
+ */
186
+ Promise.resolve().then(() => {
187
+ this.hintTextId = this.getHintTextId();
188
+ });
189
+ }
190
+ });
191
+ this.validationObserver.observe(el, {
192
+ attributes: true,
193
+ attributeFilter: ['class'],
194
+ });
195
+ }
196
+ // Always set initial state
197
+ this.isInvalid = validity.checkInvalidState(el);
157
198
  }
158
199
  componentDidLoad() {
159
200
  this.setupGesture();
@@ -164,9 +205,15 @@ const Toggle = class {
164
205
  this.gesture.destroy();
165
206
  this.gesture = undefined;
166
207
  }
208
+ // Clean up validation observer to prevent memory leaks.
209
+ if (this.validationObserver) {
210
+ this.validationObserver.disconnect();
211
+ this.validationObserver = undefined;
212
+ }
167
213
  }
168
214
  componentWillLoad() {
169
215
  this.inheritedAttributes = Object.assign({}, helpers.inheritAriaAttributes(this.el));
216
+ this.hintTextId = this.getHintTextId();
170
217
  }
171
218
  onStart() {
172
219
  this.activated = true;
@@ -189,9 +236,7 @@ const Toggle = class {
189
236
  return this.value || '';
190
237
  }
191
238
  setFocus() {
192
- if (this.focusEl) {
193
- this.focusEl.focus();
194
- }
239
+ this.el.focus();
195
240
  }
196
241
  renderOnOffSwitchLabels(mode, checked) {
197
242
  const icon = this.getSwitchLabelIcon(mode, checked);
@@ -209,9 +254,9 @@ const Toggle = class {
209
254
  get hasLabel() {
210
255
  return this.el.textContent !== '';
211
256
  }
212
- getHintTextID() {
213
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
214
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
257
+ getHintTextId() {
258
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
259
+ if (isInvalid && errorText) {
215
260
  return errorTextId;
216
261
  }
217
262
  if (helperText) {
@@ -224,7 +269,7 @@ const Toggle = class {
224
269
  * This element should only be rendered if hint text is set.
225
270
  */
226
271
  renderHintText() {
227
- const { helperText, errorText, helperTextId, errorTextId } = this;
272
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
228
273
  /**
229
274
  * undefined and empty string values should
230
275
  * be treated as not having helper/error text.
@@ -233,15 +278,15 @@ const Toggle = class {
233
278
  if (!hasHintText) {
234
279
  return;
235
280
  }
236
- return (index.h("div", { class: "toggle-bottom" }, index.h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText), index.h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText)));
281
+ return (index.h("div", { class: "toggle-bottom" }, index.h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), index.h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null)));
237
282
  }
238
283
  render() {
239
- const { activated, alignment, checked, color, disabled, el, errorTextId, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
284
+ const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
240
285
  const mode = ionicGlobal.getIonMode(this);
241
286
  const value = this.getValue();
242
287
  const rtl = dir.isRTL(el) ? 'rtl' : 'ltr';
243
288
  helpers.renderHiddenInput(true, el, name, checked ? value : '', disabled);
244
- return (index.h(index.Host, { key: '21037ea2e8326f58c84becadde475f007f931924', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === errorTextId, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, class: theme.createColorClasses(color, {
289
+ return (index.h(index.Host, { key: 'f569148edd89ee041a4719ffc4733c16b05229bd', 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: theme.createColorClasses(color, {
245
290
  [mode]: true,
246
291
  'in-item': theme.hostContext('ion-item', el),
247
292
  'toggle-activated': activated,
@@ -251,10 +296,10 @@ const Toggle = class {
251
296
  [`toggle-alignment-${alignment}`]: alignment !== undefined,
252
297
  [`toggle-label-placement-${labelPlacement}`]: true,
253
298
  [`toggle-${rtl}`]: true,
254
- }) }, index.h("label", { key: '4d153679d118d01286f6633d1c19558a97745ff6', class: "toggle-wrapper", htmlFor: inputId }, index.h("input", Object.assign({ key: '0dfcd4df15b8d41bec5ff5f8912503afbb7bec53', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, onFocus: () => this.onFocus(), onBlur: () => this.onBlur(), ref: (focusEl) => (this.focusEl = focusEl), required: required }, inheritedAttributes)), index.h("div", { key: 'ffed3a07ba2ab70e5b232e6041bc3b6b34be8331', class: {
299
+ }) }, index.h("label", { key: '3027f2ac4be6de422a14486d847fbee77f615db1', class: "toggle-wrapper", htmlFor: inputId }, index.h("input", Object.assign({ key: '4b0304c9e879e432b80184b4e5de37d55c11b436', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), index.h("div", { key: '8ef265ec942e7f01ff31cbb202ed146c6bf94e02', class: {
255
300
  'label-text-wrapper': true,
256
301
  'label-text-wrapper-hidden': !hasLabel,
257
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, index.h("slot", { key: 'd88e1e3dcdd8293f6b61f237cd7a0511dcbce300' }), this.renderHintText()), index.h("div", { key: '0e924225f5f0caf3c88738acb6c557bd8c1b68f6', class: "native-wrapper" }, this.renderToggleControl()))));
302
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, index.h("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), index.h("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
258
303
  }
259
304
  get el() { return index.getElement(this); }
260
305
  static get watchers() { return {