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
@@ -790,6 +790,28 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
790
790
  destroyKeyboardMO();
791
791
  }
792
792
  };
793
+ /**
794
+ * TODO(FW-6931): Remove this fallback upon solving the root cause
795
+ * Fallback to ensure the datetime becomes ready even if
796
+ * IntersectionObserver never reports it as intersecting.
797
+ *
798
+ * This is primarily used in environments where the observer
799
+ * might not fire as expected, such as when running under
800
+ * synthetic tests that stub IntersectionObserver.
801
+ */
802
+ this.ensureReadyIfVisible = () => {
803
+ if (this.el.classList.contains('datetime-ready')) {
804
+ return;
805
+ }
806
+ const rect = this.el.getBoundingClientRect();
807
+ if (rect.width === 0 || rect.height === 0) {
808
+ return;
809
+ }
810
+ this.initializeListeners();
811
+ writeTask(() => {
812
+ this.el.classList.add('datetime-ready');
813
+ });
814
+ };
793
815
  this.processValue = (value) => {
794
816
  const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
795
817
  const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
@@ -1107,6 +1129,17 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
1107
1129
  * triggering the `hiddenIO` observer below.
1108
1130
  */
1109
1131
  raf(() => visibleIO === null || visibleIO === void 0 ? void 0 : visibleIO.observe(intersectionTrackerRef));
1132
+ /**
1133
+ * TODO(FW-6931): Remove this fallback upon solving the root cause
1134
+ * Fallback: If IntersectionObserver never reports that the
1135
+ * datetime is visible but the host clearly has layout, ensure
1136
+ * we still initialize listeners and mark the component as ready.
1137
+ *
1138
+ * We schedule this after everything has had a chance to run.
1139
+ */
1140
+ setTimeout(() => {
1141
+ this.ensureReadyIfVisible();
1142
+ }, 100);
1110
1143
  /**
1111
1144
  * We need to clean up listeners when the datetime is hidden
1112
1145
  * in a popover/modal so that we can properly scroll containers
@@ -1862,7 +1895,7 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
1862
1895
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1863
1896
  const hasWheelVariant = hasDatePresentation && preferWheel;
1864
1897
  renderHiddenInput(true, el, name, formatValue(value), disabled);
1865
- return (h(Host, { key: '57492534800ea059a7c2bbd9f0059cc0b75ae8d2', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1898
+ return (h(Host, { key: 'efdbc0922670a841bc667ceac392cdc1dedffd01', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1866
1899
  [mode]: true,
1867
1900
  ['datetime-readonly']: readonly,
1868
1901
  ['datetime-disabled']: disabled,
@@ -1872,7 +1905,7 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
1872
1905
  [`datetime-size-${size}`]: true,
1873
1906
  [`datetime-prefer-wheel`]: hasWheelVariant,
1874
1907
  [`datetime-grid`]: isGridStyle,
1875
- })) }, h("div", { key: '97dac5e5195635ac0bc5fb472b9d09e5c3c6bbc3', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1908
+ })) }, h("div", { key: '3f8bb75fcb0baff55182ef3aa1b535eacc58d81f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1876
1909
  }
1877
1910
  get el() { return this; }
1878
1911
  static get watchers() { return {
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { proxyCustomElement, HTMLElement, createEvent, forceUpdate, Build, h, Host } from '@stencil/core/internal/client';
5
5
  import { c as createNotchController } from './notch-controller.js';
6
+ import { c as checkInvalidState } from './validity.js';
6
7
  import { l as debounceEvent, i as inheritAriaAttributes, d as inheritAttributes, c as componentOnReady } from './helpers.js';
7
8
  import { c as createSlotMutationController, g as getCounterText } from './input.utils.js';
8
9
  import { h as hostContext, c as createColorClasses } from './theme.js';
@@ -233,14 +234,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
233
234
  componentWillLoad() {
234
235
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
235
236
  }
236
- /**
237
- * Checks if the input is in an invalid state based on Ionic validation classes
238
- */
239
- checkInvalidState() {
240
- const hasIonTouched = this.el.classList.contains('ion-touched');
241
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
242
- return hasIonTouched && hasIonInvalid;
243
- }
244
237
  connectedCallback() {
245
238
  const { el } = this;
246
239
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
@@ -248,7 +241,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
248
241
  // Watch for class changes to update validation state
249
242
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
250
243
  this.validationObserver = new MutationObserver(() => {
251
- const newIsInvalid = this.checkInvalidState();
244
+ const newIsInvalid = checkInvalidState(el);
252
245
  if (this.isInvalid !== newIsInvalid) {
253
246
  this.isInvalid = newIsInvalid;
254
247
  // Force a re-render to update aria-describedby immediately
@@ -261,7 +254,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
261
254
  });
262
255
  }
263
256
  // Always set initial state
264
- this.isInvalid = this.checkInvalidState();
257
+ this.isInvalid = checkInvalidState(el);
265
258
  this.debounceChanged();
266
259
  if (Build.isBrowser) {
267
260
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -525,7 +518,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
525
518
  * TODO(FW-5592): Remove hasStartEndSlots condition
526
519
  */
527
520
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
528
- return (h(Host, { key: '8a51f0300d5bc66392f9ab9a6fa0b5d388072a33', class: createColorClasses(this.color, {
521
+ return (h(Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: createColorClasses(this.color, {
529
522
  [mode]: true,
530
523
  'has-value': hasValue,
531
524
  'has-focus': hasFocus,
@@ -536,14 +529,14 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
536
529
  'in-item': inItem,
537
530
  'in-item-color': hostContext('ion-item.ion-color', this.el),
538
531
  'input-disabled': disabled,
539
- }) }, h("label", { key: '9f8cf88d7d0e27931b51bd9c67f048c7fc6f5703', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '7ad30bf9777774062a6ccf9a3ba804f251eef1bb', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '8af0b0325d101df8eed7d24f2767d6ca4d307319', name: "start" }), 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 && (h("button", { key: 'b081d0e1ec1444b4c9cca145fc9cd2ad4a68b3da', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
532
+ }) }, h("label", { key: '353f68726ce180299bd9adc81e5ff7d26a48f54f', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '2034b4bad04fc157f3298a1805819216b6f439d0', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '96bb5e30176b2bd76dfb75bfbf6c1c3d4403f4bb', name: "start" }), 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 && (h("button", { key: '95f3df17b7691d9a2e7dcd4a51f16a94aa3ca36f', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
540
533
  /**
541
534
  * This prevents mobile browsers from
542
535
  * blurring the input when the clear
543
536
  * button is activated.
544
537
  */
545
538
  ev.preventDefault();
546
- }, onClick: this.clearTextInput }, h("ion-icon", { key: '01535299241c3635460c05646420acf62a1ff567', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '480f3eb58b08ae792866a5b9b4c068748c5567cc', name: "end" })), shouldRenderHighlight && h("div", { key: 'a8609cacee88e4a09f1cca65b6a47cb79a56f35e', class: "input-highlight" })), this.renderBottomContent()));
539
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: '16b0af75eed50c8115fb5597f73b5fbf71c2530e', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: 'c48da0f8ddb3764ac43efa705bb4a6bb2d9cc2fd', name: "end" })), shouldRenderHighlight && h("div", { key: 'f15238481fc20de56ca7ecb6e350b3c024cc755e', class: "input-highlight" })), this.renderBottomContent()));
547
540
  }
548
541
  get el() { return this; }
549
542
  static get watchers() { return {
@@ -1,9 +1,10 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { proxyCustomElement, HTMLElement, createEvent, h, Host, forceUpdate } from '@stencil/core/internal/client';
4
+ import { proxyCustomElement, HTMLElement, createEvent, Build, h, Host, forceUpdate } from '@stencil/core/internal/client';
5
5
  import { c as createNotchController } from './notch-controller.js';
6
6
  import { i as isOptionSelected, d as defineCustomElement$8, c as compareOptions } from './radio.js';
7
+ import { c as checkInvalidState } from './validity.js';
7
8
  import { d as inheritAttributes, e as renderHiddenInput, h as focusVisibleElement } from './helpers.js';
8
9
  import { p as printIonWarning } from './index4.js';
9
10
  import { c as popoverController, b as actionSheetController, a as alertController, m as modalController } from './overlays.js';
@@ -65,6 +66,10 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
65
66
  * is applied in both cases.
66
67
  */
67
68
  this.hasFocus = false;
69
+ /**
70
+ * Track validation state for proper aria-live announcements.
71
+ */
72
+ this.isInvalid = false;
68
73
  /**
69
74
  * The text to display on the cancel button.
70
75
  */
@@ -194,9 +199,46 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
194
199
  */
195
200
  forceUpdate(this);
196
201
  });
202
+ // Watch for class changes to update validation state.
203
+ if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
204
+ this.validationObserver = new MutationObserver(() => {
205
+ const newIsInvalid = checkInvalidState(this.el);
206
+ if (this.isInvalid !== newIsInvalid) {
207
+ this.isInvalid = newIsInvalid;
208
+ /**
209
+ * Screen readers tend to announce changes
210
+ * to `aria-describedby` when the attribute
211
+ * is changed during a blur event for a
212
+ * native form control.
213
+ * However, the announcement can be spotty
214
+ * when using a non-native form control
215
+ * and `forceUpdate()`.
216
+ * This is due to `forceUpdate()` internally
217
+ * rescheduling the DOM update to a lower
218
+ * priority queue regardless if it's called
219
+ * inside a Promise or not, thus causing
220
+ * the screen reader to potentially miss the
221
+ * change.
222
+ * By using a State variable inside a Promise,
223
+ * it guarantees a re-render immediately at
224
+ * a higher priority.
225
+ */
226
+ Promise.resolve().then(() => {
227
+ this.hintTextId = this.getHintTextId();
228
+ });
229
+ }
230
+ });
231
+ this.validationObserver.observe(el, {
232
+ attributes: true,
233
+ attributeFilter: ['class'],
234
+ });
235
+ }
236
+ // Always set initial state
237
+ this.isInvalid = checkInvalidState(this.el);
197
238
  }
198
239
  componentWillLoad() {
199
240
  this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
241
+ this.hintTextId = this.getHintTextId();
200
242
  }
201
243
  componentDidLoad() {
202
244
  /**
@@ -220,6 +262,11 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
220
262
  this.notchController.destroy();
221
263
  this.notchController = undefined;
222
264
  }
265
+ // Clean up validation observer to prevent memory leaks.
266
+ if (this.validationObserver) {
267
+ this.validationObserver.disconnect();
268
+ this.validationObserver = undefined;
269
+ }
223
270
  }
224
271
  /**
225
272
  * Open the select overlay. The overlay is either an alert, action sheet, or popover,
@@ -690,11 +737,11 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
690
737
  }
691
738
  renderListbox() {
692
739
  const { disabled, inputId, isExpanded, required } = this;
693
- return (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) }));
740
+ return (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) }));
694
741
  }
695
- getHintTextID() {
696
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
697
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
742
+ getHintTextId() {
743
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
744
+ if (isInvalid && errorText) {
698
745
  return errorTextId;
699
746
  }
700
747
  if (helperText) {
@@ -706,10 +753,10 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
706
753
  * Renders the helper text or error text values
707
754
  */
708
755
  renderHintText() {
709
- const { helperText, errorText, helperTextId, errorTextId } = this;
756
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
710
757
  return [
711
- h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText),
712
- h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText),
758
+ h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null),
759
+ h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null),
713
760
  ];
714
761
  }
715
762
  /**
@@ -757,7 +804,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
757
804
  * TODO(FW-5592): Remove hasStartEndSlots condition
758
805
  */
759
806
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
760
- return (h(Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: createColorClasses(this.color, {
807
+ return (h(Host, { key: '35b5e18e6f79a802ff2d46d1242e80ff755cc0b9', onClick: this.onClick, class: createColorClasses(this.color, {
761
808
  [mode]: true,
762
809
  'in-item': inItem,
763
810
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -775,7 +822,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
775
822
  [`select-justify-${justify}`]: justifyEnabled,
776
823
  [`select-shape-${shape}`]: shape !== undefined,
777
824
  [`select-label-placement-${labelPlacement}`]: true,
778
- }) }, h("label", { key: '0d0c8ec55269adcac625f2899a547f4e7f3e3741', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'f6dfc93c0e23cbe75a2947abde67d842db2dad78', class: "select-wrapper-inner" }, h("slot", { key: '957bfadf9f101f519091419a362d3abdc2be66f6', name: "start" }), h("div", { key: 'ca349202a484e7f2e884533fd330f0b136754f7d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'f0e62a6533ff1c8f62bd2d27f60b23385c4fa9ed', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: 'fb840d46bafafb09898ebeebbe8c181906a3d8a2', class: "select-highlight" })), this.renderBottomContent()));
825
+ }) }, h("label", { key: '6005b34a0c50bc4d7653a4276bc232ecd02e083c', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'c7e07aa81ae856c057f16275dd058f37c5670a47', class: "select-wrapper-inner" }, h("slot", { key: '7fc2deefe0424404caacdbbd9e08ed43ba55d28a', name: "start" }), h("div", { key: '157d74ee717b1bc30b5f1c233a09b0c8456aa68e', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'ea66db304528b82bf9317730b6dce3db2612f235', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '786eb1530b7476f0615d4e7c0bf4e7e4dc66509c', class: "select-highlight" })), this.renderBottomContent()));
779
826
  }
780
827
  get el() { return this; }
781
828
  static get watchers() { return {
@@ -813,6 +860,8 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
813
860
  "required": [4],
814
861
  "isExpanded": [32],
815
862
  "hasFocus": [32],
863
+ "isInvalid": [32],
864
+ "hintTextId": [32],
816
865
  "open": [64]
817
866
  }, undefined, {
818
867
  "disabled": ["styleChanged"],
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { proxyCustomElement, HTMLElement, createEvent, forceUpdate, Build, writeTask, h, Host } from '@stencil/core/internal/client';
5
5
  import { c as createNotchController } from './notch-controller.js';
6
+ import { c as checkInvalidState } from './validity.js';
6
7
  import { l as debounceEvent, i as inheritAriaAttributes, d as inheritAttributes, c as componentOnReady } from './helpers.js';
7
8
  import { c as createSlotMutationController, g as getCounterText } from './input.utils.js';
8
9
  import { h as hostContext, c as createColorClasses } from './theme.js';
@@ -192,14 +193,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
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 = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
@@ -207,7 +200,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
207
200
  // Watch for class changes to update validation state
208
201
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
209
202
  this.validationObserver = new MutationObserver(() => {
210
- const newIsInvalid = this.checkValidationState();
203
+ const newIsInvalid = 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 = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
220
213
  });
221
214
  }
222
215
  // Always set initial state
223
- this.isInvalid = this.checkValidationState();
216
+ this.isInvalid = checkInvalidState(this.el);
224
217
  this.debounceChanged();
225
218
  if (Build.isBrowser) {
226
219
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -484,7 +477,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
484
477
  * TODO(FW-5592): Remove hasStartEndSlots condition
485
478
  */
486
479
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
487
- return (h(Host, { key: '26b46666a92b3f652775bb1c46661f9a30392104', class: createColorClasses(this.color, {
480
+ return (h(Host, { key: 'a70a62d7aae3831a50acd74f60b930925ada1326', class: createColorClasses(this.color, {
488
481
  [mode]: true,
489
482
  'has-value': hasValue,
490
483
  'has-focus': hasFocus,
@@ -493,7 +486,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
493
486
  [`textarea-shape-${shape}`]: shape !== undefined,
494
487
  [`textarea-label-placement-${labelPlacement}`]: true,
495
488
  'textarea-disabled': disabled,
496
- }) }, h("label", { key: '2649da816216959ebe1f34cafd9dedbac20ec3c2', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'dca98593efece1b044dbcda045fa70882d715cb2', class: "textarea-wrapper-inner" }, h("div", { key: '2019daf87fddca5ec0b2e336f0376fd9642bae1b', class: "start-slot-wrapper" }, h("slot", { key: '36c423c394a71d08261705b9d6729e756bf65924', name: "start" })), h("div", { key: '0c3ea34105c7eddfa4094371c5d288c50ed10db3', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, 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)), h("div", { key: '756e343cfd208bb5ad9ecf08d77cbb0a9606dc7b', class: "end-slot-wrapper" }, h("slot", { key: '0eb596814a037fa4634ff8c5bac0045540edfe21', name: "end" }))), shouldRenderHighlight && h("div", { key: 'df62f896eb6e0e2d1217aa487c198eb82a52bcb8', class: "textarea-highlight" })), this.renderBottomContent()));
489
+ }) }, h("label", { key: '8a2dd59a60f7469df84018eb0ede3a9ec3862703', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '1bfc368236e3da7a225a45118c27fbfc1fe5fa46', class: "textarea-wrapper-inner" }, h("div", { key: '215cbb2635ff52e31a8973376989b85e7245d40f', class: "start-slot-wrapper" }, h("slot", { key: '9f6b461cdee9d629deb695d2bea054ece2f32305', name: "start" })), h("div", { key: 'c1af35a2d5bc452bebe0b22a26d15ff52b4e9fc8', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, 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)), h("div", { key: 'c053ea8b865d0e29763aed2e4939cc9c9e374c15', class: "end-slot-wrapper" }, h("slot", { key: '930aa641833b0df54b9ea10368fc2f46d5f491f6', name: "end" }))), shouldRenderHighlight && h("div", { key: '8d12597d15f5f429d80e8272ea99e64ed924e482', class: "textarea-highlight" })), this.renderBottomContent()));
497
490
  }
498
491
  get el() { return this; }
499
492
  static get watchers() { return {
@@ -1,8 +1,9 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
4
+ import { proxyCustomElement, HTMLElement, createEvent, Build, h, Host } from '@stencil/core/internal/client';
5
5
  import { i as inheritAriaAttributes, e as renderHiddenInput } from './helpers.js';
6
+ import { c as checkInvalidState } from './validity.js';
6
7
  import { d as hapticSelection } from './haptic.js';
7
8
  import { a as isPlatform, b as getIonMode } from './ionic-global.js';
8
9
  import { i as isRTL } from './dir.js';
@@ -33,6 +34,10 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
33
34
  this.inheritedAttributes = {};
34
35
  this.didLoad = false;
35
36
  this.activated = false;
37
+ /**
38
+ * Track validation state for proper aria-live announcements.
39
+ */
40
+ this.isInvalid = false;
36
41
  /**
37
42
  * The name of the control, which is submitted with the form data.
38
43
  */
@@ -140,22 +145,58 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
140
145
  const { checked, value } = this;
141
146
  const isNowChecked = !checked;
142
147
  this.checked = isNowChecked;
143
- this.setFocus();
144
148
  this.ionChange.emit({
145
149
  checked: isNowChecked,
146
150
  value,
147
151
  });
148
152
  }
149
153
  async connectedCallback() {
154
+ const { didLoad, el } = this;
150
155
  /**
151
156
  * If we have not yet rendered
152
157
  * ion-toggle, then toggleTrack is not defined.
153
158
  * But if we are moving ion-toggle via appendChild,
154
159
  * then toggleTrack will be defined.
155
160
  */
156
- if (this.didLoad) {
161
+ if (didLoad) {
157
162
  this.setupGesture();
158
163
  }
164
+ // Watch for class changes to update validation state.
165
+ if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
166
+ this.validationObserver = new MutationObserver(() => {
167
+ const newIsInvalid = checkInvalidState(el);
168
+ if (this.isInvalid !== newIsInvalid) {
169
+ this.isInvalid = newIsInvalid;
170
+ /**
171
+ * Screen readers tend to announce changes
172
+ * to `aria-describedby` when the attribute
173
+ * is changed during a blur event for a
174
+ * native form control.
175
+ * However, the announcement can be spotty
176
+ * when using a non-native form control
177
+ * and `forceUpdate()`.
178
+ * This is due to `forceUpdate()` internally
179
+ * rescheduling the DOM update to a lower
180
+ * priority queue regardless if it's called
181
+ * inside a Promise or not, thus causing
182
+ * the screen reader to potentially miss the
183
+ * change.
184
+ * By using a State variable inside a Promise,
185
+ * it guarantees a re-render immediately at
186
+ * a higher priority.
187
+ */
188
+ Promise.resolve().then(() => {
189
+ this.hintTextId = this.getHintTextId();
190
+ });
191
+ }
192
+ });
193
+ this.validationObserver.observe(el, {
194
+ attributes: true,
195
+ attributeFilter: ['class'],
196
+ });
197
+ }
198
+ // Always set initial state
199
+ this.isInvalid = checkInvalidState(el);
159
200
  }
160
201
  componentDidLoad() {
161
202
  this.setupGesture();
@@ -166,9 +207,15 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
166
207
  this.gesture.destroy();
167
208
  this.gesture = undefined;
168
209
  }
210
+ // Clean up validation observer to prevent memory leaks.
211
+ if (this.validationObserver) {
212
+ this.validationObserver.disconnect();
213
+ this.validationObserver = undefined;
214
+ }
169
215
  }
170
216
  componentWillLoad() {
171
217
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
218
+ this.hintTextId = this.getHintTextId();
172
219
  }
173
220
  onStart() {
174
221
  this.activated = true;
@@ -191,9 +238,7 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
191
238
  return this.value || '';
192
239
  }
193
240
  setFocus() {
194
- if (this.focusEl) {
195
- this.focusEl.focus();
196
- }
241
+ this.el.focus();
197
242
  }
198
243
  renderOnOffSwitchLabels(mode, checked) {
199
244
  const icon = this.getSwitchLabelIcon(mode, checked);
@@ -211,9 +256,9 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
211
256
  get hasLabel() {
212
257
  return this.el.textContent !== '';
213
258
  }
214
- getHintTextID() {
215
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
216
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
259
+ getHintTextId() {
260
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
261
+ if (isInvalid && errorText) {
217
262
  return errorTextId;
218
263
  }
219
264
  if (helperText) {
@@ -226,7 +271,7 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
226
271
  * This element should only be rendered if hint text is set.
227
272
  */
228
273
  renderHintText() {
229
- const { helperText, errorText, helperTextId, errorTextId } = this;
274
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
230
275
  /**
231
276
  * undefined and empty string values should
232
277
  * be treated as not having helper/error text.
@@ -235,15 +280,15 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
235
280
  if (!hasHintText) {
236
281
  return;
237
282
  }
238
- return (h("div", { class: "toggle-bottom" }, h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText), h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText)));
283
+ return (h("div", { class: "toggle-bottom" }, h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null)));
239
284
  }
240
285
  render() {
241
- const { activated, alignment, checked, color, disabled, el, errorTextId, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
286
+ const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
242
287
  const mode = getIonMode(this);
243
288
  const value = this.getValue();
244
289
  const rtl = isRTL(el) ? 'rtl' : 'ltr';
245
290
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
246
- return (h(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: createColorClasses(color, {
291
+ return (h(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: createColorClasses(color, {
247
292
  [mode]: true,
248
293
  'in-item': hostContext('ion-item', el),
249
294
  'toggle-activated': activated,
@@ -253,10 +298,10 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
253
298
  [`toggle-alignment-${alignment}`]: alignment !== undefined,
254
299
  [`toggle-label-placement-${labelPlacement}`]: true,
255
300
  [`toggle-${rtl}`]: true,
256
- }) }, h("label", { key: '4d153679d118d01286f6633d1c19558a97745ff6', class: "toggle-wrapper", htmlFor: inputId }, 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)), h("div", { key: 'ffed3a07ba2ab70e5b232e6041bc3b6b34be8331', class: {
301
+ }) }, h("label", { key: '3027f2ac4be6de422a14486d847fbee77f615db1', class: "toggle-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: '4b0304c9e879e432b80184b4e5de37d55c11b436', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), h("div", { key: '8ef265ec942e7f01ff31cbb202ed146c6bf94e02', class: {
257
302
  'label-text-wrapper': true,
258
303
  'label-text-wrapper-hidden': !hasLabel,
259
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: 'd88e1e3dcdd8293f6b61f237cd7a0511dcbce300' }), this.renderHintText()), h("div", { key: '0e924225f5f0caf3c88738acb6c557bd8c1b68f6', class: "native-wrapper" }, this.renderToggleControl()))));
304
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), h("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
260
305
  }
261
306
  get el() { return this; }
262
307
  static get watchers() { return {
@@ -279,7 +324,9 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
279
324
  "justify": [1],
280
325
  "alignment": [1],
281
326
  "required": [4],
282
- "activated": [32]
327
+ "activated": [32],
328
+ "isInvalid": [32],
329
+ "hintTextId": [32]
283
330
  }, undefined, {
284
331
  "disabled": ["disabledChanged"]
285
332
  }]);
@@ -1,8 +1,9 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
4
+ import { proxyCustomElement, HTMLElement, createEvent, Build, h, Host } from '@stencil/core/internal/client';
5
5
  import { e as renderHiddenInput } from './helpers.js';
6
+ import { c as checkInvalidState } from './validity.js';
6
7
  import { b as getIonMode } from './ionic-global.js';
7
8
 
8
9
  const radioGroupIosCss = "ion-radio-group{vertical-align:top}.radio-group-wrapper{display:inline}.radio-group-top{line-height:1.5}.radio-group-top .error-text{display:none;color:var(--ion-color-danger, #c5000f)}.radio-group-top .helper-text{display:block;color:var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d))}.ion-touched.ion-invalid .radio-group-top .error-text{display:block}.ion-touched.ion-invalid .radio-group-top .helper-text{display:none}ion-list .radio-group-top{-webkit-padding-start:16px;padding-inline-start:16px;-webkit-padding-end:16px;padding-inline-end:16px}";
@@ -21,6 +22,10 @@ const RadioGroup = /*@__PURE__*/ proxyCustomElement(class RadioGroup extends HTM
21
22
  this.helperTextId = `${this.inputId}-helper-text`;
22
23
  this.errorTextId = `${this.inputId}-error-text`;
23
24
  this.labelId = `${this.inputId}-lbl`;
25
+ /**
26
+ * Track validation state for proper aria-live announcements.
27
+ */
28
+ this.isInvalid = false;
24
29
  /**
25
30
  * If `true`, the radios can be deselected.
26
31
  */
@@ -102,6 +107,52 @@ const RadioGroup = /*@__PURE__*/ proxyCustomElement(class RadioGroup extends HTM
102
107
  this.labelId = label.id = this.name + '-lbl';
103
108
  }
104
109
  }
110
+ // Watch for class changes to update validation state.
111
+ if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
112
+ this.validationObserver = new MutationObserver(() => {
113
+ const newIsInvalid = checkInvalidState(this.el);
114
+ if (this.isInvalid !== newIsInvalid) {
115
+ this.isInvalid = newIsInvalid;
116
+ /**
117
+ * Screen readers tend to announce changes
118
+ * to `aria-describedby` when the attribute
119
+ * is changed during a blur event for a
120
+ * native form control.
121
+ * However, the announcement can be spotty
122
+ * when using a non-native form control
123
+ * and `forceUpdate()`.
124
+ * This is due to `forceUpdate()` internally
125
+ * rescheduling the DOM update to a lower
126
+ * priority queue regardless if it's called
127
+ * inside a Promise or not, thus causing
128
+ * the screen reader to potentially miss the
129
+ * change.
130
+ * By using a State variable inside a Promise,
131
+ * it guarantees a re-render immediately at
132
+ * a higher priority.
133
+ */
134
+ Promise.resolve().then(() => {
135
+ this.hintTextId = this.getHintTextId();
136
+ });
137
+ }
138
+ });
139
+ this.validationObserver.observe(this.el, {
140
+ attributes: true,
141
+ attributeFilter: ['class'],
142
+ });
143
+ }
144
+ // Always set initial state
145
+ this.isInvalid = checkInvalidState(this.el);
146
+ }
147
+ componentWillLoad() {
148
+ this.hintTextId = this.getHintTextId();
149
+ }
150
+ disconnectedCallback() {
151
+ // Clean up validation observer to prevent memory leaks.
152
+ if (this.validationObserver) {
153
+ this.validationObserver.disconnect();
154
+ this.validationObserver = undefined;
155
+ }
105
156
  }
106
157
  getRadios() {
107
158
  return Array.from(this.el.querySelectorAll('ion-radio'));
@@ -177,16 +228,16 @@ const RadioGroup = /*@__PURE__*/ proxyCustomElement(class RadioGroup extends HTM
177
228
  * Renders the helper text or error text values
178
229
  */
179
230
  renderHintText() {
180
- const { helperText, errorText, helperTextId, errorTextId } = this;
231
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
181
232
  const hasHintText = !!helperText || !!errorText;
182
233
  if (!hasHintText) {
183
234
  return;
184
235
  }
185
- return (h("div", { class: "radio-group-top" }, h("div", { id: helperTextId, class: "helper-text" }, helperText), h("div", { id: errorTextId, class: "error-text" }, errorText)));
236
+ return (h("div", { class: "radio-group-top" }, h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), h("div", { id: errorTextId, class: "error-text", role: "alert" }, isInvalid ? errorText : null)));
186
237
  }
187
- getHintTextID() {
188
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
189
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
238
+ getHintTextId() {
239
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
240
+ if (isInvalid && errorText) {
190
241
  return errorTextId;
191
242
  }
192
243
  if (helperText) {
@@ -198,7 +249,7 @@ const RadioGroup = /*@__PURE__*/ proxyCustomElement(class RadioGroup extends HTM
198
249
  const { label, labelId, el, name, value } = this;
199
250
  const mode = getIonMode(this);
200
251
  renderHiddenInput(true, el, name, value, false);
201
- return (h(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(), h("div", { key: '45b09efc10776b889a8f372cba80d25a3fc849da', class: "radio-group-wrapper" }, h("slot", { key: '58714934542c2fdd7396de160364f3f06b32e8f8' }))));
252
+ return (h(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(), h("div", { key: '85045b45a0100a45f3b9a35d1c5a25ec63d525c4', class: "radio-group-wrapper" }, h("slot", { key: '53dacb87ce62398e78771fb2efaf839ab922d946' }))));
202
253
  }
203
254
  get el() { return this; }
204
255
  static get watchers() { return {
@@ -215,6 +266,8 @@ const RadioGroup = /*@__PURE__*/ proxyCustomElement(class RadioGroup extends HTM
215
266
  "value": [1032],
216
267
  "helperText": [1, "helper-text"],
217
268
  "errorText": [1, "error-text"],
269
+ "isInvalid": [32],
270
+ "hintTextId": [32],
218
271
  "setFocus": [64]
219
272
  }, [[4, "keydown", "onKeydown"]], {
220
273
  "value": ["valueChanged"]