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
@@ -0,0 +1,17 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ /**
5
+ * Checks if the form element is in an invalid state based on
6
+ * Ionic validation classes.
7
+ *
8
+ * @param el The form element to check.
9
+ * @returns `true` if the element is invalid, `false` otherwise.
10
+ */
11
+ const checkInvalidState = (el) => {
12
+ const hasIonTouched = el.classList.contains('ion-touched');
13
+ const hasIonInvalid = el.classList.contains('ion-invalid');
14
+ return hasIonTouched && hasIonInvalid;
15
+ };
16
+
17
+ export { checkInvalidState as c };
@@ -120,15 +120,26 @@ const createFocusController = () => {
120
120
  };
121
121
  const LAST_FOCUS = 'ion-last-focus';
122
122
 
123
- const iosTransitionAnimation = () => Promise.resolve().then(function () { return require('./ios.transition-j9CclgEW.js'); });
124
- const mdTransitionAnimation = () => Promise.resolve().then(function () { return require('./md.transition-CwFyRSfv.js'); });
123
+ const iosTransitionAnimation = () => Promise.resolve().then(function () { return require('./ios.transition-BOt_uW73.js'); });
124
+ const mdTransitionAnimation = () => Promise.resolve().then(function () { return require('./md.transition-Dt968VXB.js'); });
125
125
  const focusController = createFocusController();
126
126
  // TODO(FW-2832): types
127
+ /**
128
+ * Executes the main page transition.
129
+ * It also manages the lifecycle of header visibility (if any)
130
+ * to prevent visual flickering in iOS. The flickering only
131
+ * occurs for a condensed header that is placed above the content.
132
+ *
133
+ * @param opts Options for the transition.
134
+ * @returns A promise that resolves when the transition is complete.
135
+ */
127
136
  const transition = (opts) => {
128
137
  return new Promise((resolve, reject) => {
129
138
  index.writeTask(() => {
130
- beforeTransition(opts);
131
- runTransition(opts).then((result) => {
139
+ const transitioningInactiveHeader = getIosIonHeader(opts);
140
+ beforeTransition(opts, transitioningInactiveHeader);
141
+ runTransition(opts)
142
+ .then((result) => {
132
143
  if (result.animation) {
133
144
  result.animation.destroy();
134
145
  }
@@ -137,15 +148,21 @@ const transition = (opts) => {
137
148
  }, (error) => {
138
149
  afterTransition(opts);
139
150
  reject(error);
151
+ })
152
+ .finally(() => {
153
+ // Ensure that the header is restored to its original state.
154
+ setHeaderTransitionClass(transitioningInactiveHeader, false);
140
155
  });
141
156
  });
142
157
  });
143
158
  };
144
- const beforeTransition = (opts) => {
159
+ const beforeTransition = (opts, transitioningInactiveHeader) => {
145
160
  const enteringEl = opts.enteringEl;
146
161
  const leavingEl = opts.leavingEl;
147
162
  focusController.saveViewFocus(leavingEl);
148
163
  setZIndex(enteringEl, leavingEl, opts.direction);
164
+ // Prevent flickering of the header by adding a class.
165
+ setHeaderTransitionClass(transitioningInactiveHeader, true);
149
166
  if (opts.showGoBack) {
150
167
  enteringEl.classList.add('can-go-back');
151
168
  }
@@ -334,6 +351,39 @@ const setZIndex = (enteringEl, leavingEl, direction) => {
334
351
  leavingEl.style.zIndex = '100';
335
352
  }
336
353
  };
354
+ /**
355
+ * Add a class to ensure that the header (if any)
356
+ * does not flicker during the transition. By adding the
357
+ * transitioning class, we ensure that the header has
358
+ * the necessary styles to prevent the following flickers:
359
+ * 1. When entering a page with a condensed header, the
360
+ * header should never be visible. However,
361
+ * it briefly renders the background color while
362
+ * the transition is occurring.
363
+ * 2. When leaving a page with a condensed header, the
364
+ * header has an opacity of 0 and the pages
365
+ * have a z-index which causes the entering page to
366
+ * briefly show it's content underneath the leaving page.
367
+ * 3. When entering a page or leaving a page with a fade
368
+ * header, the header should not have a background color.
369
+ * However, it briefly shows the background color while
370
+ * the transition is occurring.
371
+ *
372
+ * @param header The header element to modify.
373
+ * @param isTransitioning Whether the transition is occurring.
374
+ */
375
+ const setHeaderTransitionClass = (header, isTransitioning) => {
376
+ if (!header) {
377
+ return;
378
+ }
379
+ const transitionClass = 'header-transitioning';
380
+ if (isTransitioning) {
381
+ header.classList.add(transitionClass);
382
+ }
383
+ else {
384
+ header.classList.remove(transitionClass);
385
+ }
386
+ };
337
387
  const getIonPageElement = (element) => {
338
388
  if (element.classList.contains('ion-page')) {
339
389
  return element;
@@ -345,6 +395,27 @@ const getIonPageElement = (element) => {
345
395
  // idk, return the original element so at least something animates and we don't have a null pointer
346
396
  return element;
347
397
  };
398
+ /**
399
+ * Retrieves the ion-header element from a page based on the
400
+ * direction of the transition.
401
+ *
402
+ * @param opts Options for the transition.
403
+ * @returns The ion-header element or null if not found or not in 'ios' mode.
404
+ */
405
+ const getIosIonHeader = (opts) => {
406
+ const enteringEl = opts.enteringEl;
407
+ const leavingEl = opts.leavingEl;
408
+ const direction = opts.direction;
409
+ const mode = opts.mode;
410
+ if (mode !== 'ios') {
411
+ return null;
412
+ }
413
+ const element = direction === 'back' ? leavingEl : enteringEl;
414
+ if (!element) {
415
+ return null;
416
+ }
417
+ return element.querySelector('ion-header');
418
+ };
348
419
 
349
420
  exports.LIFECYCLE_DID_ENTER = LIFECYCLE_DID_ENTER;
350
421
  exports.LIFECYCLE_DID_LEAVE = LIFECYCLE_DID_LEAVE;
@@ -4,9 +4,9 @@
4
4
  'use strict';
5
5
 
6
6
  var animation = require('./animation-Bt3H9L1C.js');
7
- var index = require('./index-CD5Rjp23.js');
8
- var ios_transition = require('./ios.transition-j9CclgEW.js');
9
- var md_transition = require('./md.transition-CwFyRSfv.js');
7
+ var index = require('./index-094mMFB-.js');
8
+ var ios_transition = require('./ios.transition-BOt_uW73.js');
9
+ var md_transition = require('./md.transition-Dt968VXB.js');
10
10
  var cubicBezier = require('./cubic-bezier-DAjy1V-e.js');
11
11
  var index$1 = require('./index-CAvQ7Tka.js');
12
12
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
@@ -15,10 +15,57 @@ const accordionMdCss = ":host{display:block;position:relative;width:100%;backgro
15
15
  const Accordion = class {
16
16
  constructor(hostRef) {
17
17
  index.registerInstance(this, hostRef);
18
- this.updateListener = () => this.updateState(false);
18
+ this.accordionGroupUpdateHandler = () => {
19
+ /**
20
+ * Determine if this update will cause an actual state change.
21
+ * We only want to mark as "interacted" if the state is changing.
22
+ */
23
+ const accordionGroup = this.accordionGroupEl;
24
+ if (accordionGroup) {
25
+ const value = accordionGroup.value;
26
+ const accordionValue = this.value;
27
+ const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
28
+ const isExpanded = this.state === 4 /* AccordionState.Expanded */ || this.state === 8 /* AccordionState.Expanding */;
29
+ const stateWillChange = shouldExpand !== isExpanded;
30
+ /**
31
+ * Only mark as interacted if:
32
+ * 1. This is not the first update we've received with a defined value
33
+ * 2. The state is actually changing (prevents redundant updates from enabling animations)
34
+ */
35
+ if (this.hasReceivedFirstUpdate && stateWillChange) {
36
+ this.hasInteracted = true;
37
+ }
38
+ /**
39
+ * Only count this as the first update if the group value is defined.
40
+ * This prevents the initial undefined value from the group's componentDidLoad
41
+ * from being treated as the first real update.
42
+ */
43
+ if (value !== undefined) {
44
+ this.hasReceivedFirstUpdate = true;
45
+ }
46
+ }
47
+ this.updateState();
48
+ };
19
49
  this.state = 1 /* AccordionState.Collapsed */;
20
50
  this.isNext = false;
21
51
  this.isPrevious = false;
52
+ /**
53
+ * Tracks whether a user-initiated interaction has occurred.
54
+ * Animations are disabled until the first interaction happens.
55
+ * This prevents the accordion from animating when it's programmatically
56
+ * set to an expanded or collapsed state on initial load.
57
+ */
58
+ this.hasInteracted = false;
59
+ /**
60
+ * Tracks if this accordion has ever been expanded.
61
+ * Used to prevent the first expansion from animating.
62
+ */
63
+ this.hasEverBeenExpanded = false;
64
+ /**
65
+ * Tracks if this accordion has received its first update from the group.
66
+ * Used to distinguish initial programmatic sets from user interactions.
67
+ */
68
+ this.hasReceivedFirstUpdate = false;
22
69
  /**
23
70
  * The value of the accordion. Defaults to an autogenerated
24
71
  * value.
@@ -123,10 +170,15 @@ const Accordion = class {
123
170
  iconEl.setAttribute('aria-hidden', 'true');
124
171
  ionItem.appendChild(iconEl);
125
172
  };
126
- this.expandAccordion = (initialUpdate = false) => {
173
+ this.expandAccordion = () => {
127
174
  const { contentEl, contentElWrapper } = this;
128
- if (initialUpdate || contentEl === undefined || contentElWrapper === undefined) {
175
+ /**
176
+ * If the content elements aren't available yet, just set the state.
177
+ * This happens on initial render before the DOM is ready.
178
+ */
179
+ if (contentEl === undefined || contentElWrapper === undefined) {
129
180
  this.state = 4 /* AccordionState.Expanded */;
181
+ this.hasEverBeenExpanded = true;
130
182
  return;
131
183
  }
132
184
  if (this.state === 4 /* AccordionState.Expanded */) {
@@ -135,6 +187,11 @@ const Accordion = class {
135
187
  if (this.currentRaf !== undefined) {
136
188
  cancelAnimationFrame(this.currentRaf);
137
189
  }
190
+ /**
191
+ * Mark that this accordion has been expanded at least once.
192
+ * This allows subsequent expansions to animate.
193
+ */
194
+ this.hasEverBeenExpanded = true;
138
195
  if (this.shouldAnimate()) {
139
196
  helpers.raf(() => {
140
197
  this.state = 8 /* AccordionState.Expanding */;
@@ -152,9 +209,13 @@ const Accordion = class {
152
209
  this.state = 4 /* AccordionState.Expanded */;
153
210
  }
154
211
  };
155
- this.collapseAccordion = (initialUpdate = false) => {
212
+ this.collapseAccordion = () => {
156
213
  const { contentEl } = this;
157
- if (initialUpdate || contentEl === undefined) {
214
+ /**
215
+ * If the content element isn't available yet, just set the state.
216
+ * This happens on initial render before the DOM is ready.
217
+ */
218
+ if (contentEl === undefined) {
158
219
  this.state = 1 /* AccordionState.Collapsed */;
159
220
  return;
160
221
  }
@@ -189,6 +250,18 @@ const Accordion = class {
189
250
  * of what is set in the config.
190
251
  */
191
252
  this.shouldAnimate = () => {
253
+ /**
254
+ * Don't animate until after the first user interaction.
255
+ * This prevents animations on initial load when accordions
256
+ * start in an expanded or collapsed state programmatically.
257
+ *
258
+ * Additionally, don't animate the very first expansion even if
259
+ * hasInteracted is true. This handles edge cases like React StrictMode
260
+ * where effects run twice and might incorrectly mark as interacted.
261
+ */
262
+ if (!this.hasInteracted || !this.hasEverBeenExpanded) {
263
+ return false;
264
+ }
192
265
  if (typeof window === 'undefined') {
193
266
  return false;
194
267
  }
@@ -205,7 +278,7 @@ const Accordion = class {
205
278
  }
206
279
  return true;
207
280
  };
208
- this.updateState = async (initialUpdate = false) => {
281
+ this.updateState = async () => {
209
282
  const accordionGroup = this.accordionGroupEl;
210
283
  const accordionValue = this.value;
211
284
  if (!accordionGroup) {
@@ -214,11 +287,11 @@ const Accordion = class {
214
287
  const value = accordionGroup.value;
215
288
  const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
216
289
  if (shouldExpand) {
217
- this.expandAccordion(initialUpdate);
290
+ this.expandAccordion();
218
291
  this.isNext = this.isPrevious = false;
219
292
  }
220
293
  else {
221
- this.collapseAccordion(initialUpdate);
294
+ this.collapseAccordion();
222
295
  /**
223
296
  * When using popout or inset,
224
297
  * the collapsed accordion items
@@ -266,14 +339,14 @@ const Accordion = class {
266
339
  var _a;
267
340
  const accordionGroupEl = (this.accordionGroupEl = (_a = this.el) === null || _a === void 0 ? void 0 : _a.closest('ion-accordion-group'));
268
341
  if (accordionGroupEl) {
269
- this.updateState(true);
270
- helpers.addEventListener(accordionGroupEl, 'ionValueChange', this.updateListener);
342
+ this.updateState();
343
+ helpers.addEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler);
271
344
  }
272
345
  }
273
346
  disconnectedCallback() {
274
347
  const accordionGroupEl = this.accordionGroupEl;
275
348
  if (accordionGroupEl) {
276
- helpers.removeEventListener(accordionGroupEl, 'ionValueChange', this.updateListener);
349
+ helpers.removeEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler);
277
350
  }
278
351
  }
279
352
  componentDidLoad() {
@@ -297,6 +370,11 @@ const Accordion = class {
297
370
  const { accordionGroupEl, disabled, readonly, value, state } = this;
298
371
  if (disabled || readonly)
299
372
  return;
373
+ /**
374
+ * Mark that the user has interacted with the accordion.
375
+ * This enables animations for all future state changes.
376
+ */
377
+ this.hasInteracted = true;
300
378
  if (accordionGroupEl) {
301
379
  /**
302
380
  * Because the accordion group may or may
@@ -317,7 +395,7 @@ const Accordion = class {
317
395
  const headerPart = expanded ? 'header expanded' : 'header';
318
396
  const contentPart = expanded ? 'content expanded' : 'content';
319
397
  this.setAria(expanded);
320
- return (index.h(index.Host, { key: '073e1d02c18dcbc20c68648426e87c14750c031d', class: {
398
+ return (index.h(index.Host, { key: '9c90bce01eff7e5774a19f69c872f3761d66cf3c', class: {
321
399
  [mode]: true,
322
400
  'accordion-expanding': this.state === 8 /* AccordionState.Expanding */,
323
401
  'accordion-expanded': this.state === 4 /* AccordionState.Expanded */,
@@ -328,7 +406,7 @@ const Accordion = class {
328
406
  'accordion-disabled': disabled,
329
407
  'accordion-readonly': readonly,
330
408
  'accordion-animated': this.shouldAnimate(),
331
- } }, index.h("div", { key: '9b4cf326de8bb6b4033992903c0c1bfd7eea9bcc', onClick: () => this.toggleExpanded(), id: "header", part: headerPart, "aria-controls": "content", ref: (headerEl) => (this.headerEl = headerEl) }, index.h("slot", { key: '464c32a37f64655eacf4218284214f5f30b14a1e', name: "header" })), index.h("div", { key: '8bb52e6a62d7de0106b253201a89a32e79d9a594', id: "content", part: contentPart, role: "region", "aria-labelledby": "header", ref: (contentEl) => (this.contentEl = contentEl) }, index.h("div", { key: '1d9dfd952ad493754aaeea7a8f625b33c2dd90a0', id: "content-wrapper", ref: (contentElWrapper) => (this.contentElWrapper = contentElWrapper) }, index.h("slot", { key: '970dfbc55a612d739d0ca3b7b1a08e5c96d0c479', name: "content" })))));
409
+ } }, index.h("div", { key: 'cab40d5bcf3c93fd78e70b6d3906a541e725837d', onClick: () => this.toggleExpanded(), id: "header", part: headerPart, "aria-controls": "content", ref: (headerEl) => (this.headerEl = headerEl) }, index.h("slot", { key: '672bc7fb3f9e18076b41e20fc9eaeab7cafcf3a2', name: "header" })), index.h("div", { key: 'fd777ca5b4ab04aa4f44c339d58c8cd987c52bcb', id: "content", part: contentPart, role: "region", "aria-labelledby": "header", ref: (contentEl) => (this.contentEl = contentEl) }, index.h("div", { key: '0aad70a71e2cd2c16b2e98fa0bdd40421d95fe16', id: "content-wrapper", ref: (contentElWrapper) => (this.contentElWrapper = contentElWrapper) }, index.h("slot", { key: 'd630e10ac7c56b4dbf943b523f26759b83aead55', name: "content" })))));
332
410
  }
333
411
  static get delegatesFocus() { return true; }
334
412
  get el() { return index.getElement(this); }
@@ -14,7 +14,7 @@ var keyboardController = require('./keyboard-controller-GXBiBRKS.js');
14
14
  var cubicBezier = require('./cubic-bezier-DAjy1V-e.js');
15
15
  var frameworkDelegate = require('./framework-delegate-DMJRBuDi.js');
16
16
  var lockController = require('./lock-controller-aDB9wrEf.js');
17
- var index$2 = require('./index-CD5Rjp23.js');
17
+ var index$2 = require('./index-094mMFB-.js');
18
18
  require('./index-DkNv4J_i.js');
19
19
  require('./keyboard-UuAS4D_9.js');
20
20
  require('./capacitor-DmA66EwP.js');
@@ -710,6 +710,8 @@ Footer.style = {
710
710
  };
711
711
 
712
712
  const TRANSITION = 'all 0.2s ease-in-out';
713
+ const ROLE_NONE = 'none';
714
+ const ROLE_BANNER = 'banner';
713
715
  const cloneElement = (tagName) => {
714
716
  const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
715
717
  if (getCachedEl !== null) {
@@ -836,6 +838,7 @@ const setHeaderActive = (headerIndex, active = true) => {
836
838
  const toolbars = headerIndex.toolbars;
837
839
  const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
838
840
  if (active) {
841
+ headerEl.setAttribute('role', ROLE_BANNER);
839
842
  headerEl.classList.remove('header-collapse-condense-inactive');
840
843
  ionTitles.forEach((ionTitle) => {
841
844
  if (ionTitle) {
@@ -844,6 +847,16 @@ const setHeaderActive = (headerIndex, active = true) => {
844
847
  });
845
848
  }
846
849
  else {
850
+ /**
851
+ * There can only be one banner landmark per page.
852
+ * By default, all ion-headers have the banner role.
853
+ * This causes an accessibility issue when using a
854
+ * condensed header since there are two ion-headers
855
+ * on the page at once (active and inactive).
856
+ * To solve this, the role needs to be toggled
857
+ * based on which header is active.
858
+ */
859
+ headerEl.setAttribute('role', ROLE_NONE);
847
860
  headerEl.classList.add('header-collapse-condense-inactive');
848
861
  /**
849
862
  * The small title should only be accessed by screen readers
@@ -903,8 +916,32 @@ const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
903
916
  });
904
917
  });
905
918
  };
919
+ /**
920
+ * Get the role type for the ion-header.
921
+ *
922
+ * @param isInsideMenu If ion-header is inside ion-menu.
923
+ * @param isCondensed If ion-header has collapse="condense".
924
+ * @param mode The current mode.
925
+ * @returns 'none' if inside ion-menu or if condensed in md
926
+ * mode, otherwise 'banner'.
927
+ */
928
+ const getRoleType = (isInsideMenu, isCondensed, mode) => {
929
+ // If the header is inside a menu, it should not have the banner role.
930
+ if (isInsideMenu) {
931
+ return ROLE_NONE;
932
+ }
933
+ /**
934
+ * Only apply role="none" to `md` mode condensed headers
935
+ * since the large header is never shown.
936
+ */
937
+ if (isCondensed && mode === 'md') {
938
+ return ROLE_NONE;
939
+ }
940
+ // Default to banner role.
941
+ return ROLE_BANNER;
942
+ };
906
943
 
907
- const headerIosCss = "ion-header{display:block;position:relative;-ms-flex-order:-1;order:-1;width:100%;z-index:10}ion-header ion-toolbar:first-of-type{padding-top:var(--ion-safe-area-top, 0)}.header-ios ion-toolbar:last-of-type{--border-width:0 0 0.55px}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){.header-background{left:0;right:0;top:0;bottom:0;position:absolute;-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}.header-translucent-ios ion-toolbar{--opacity:.8}.header-collapse-condense-inactive .header-background{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}}.header-ios.ion-no-border ion-toolbar:last-of-type{--border-width:0}.header-collapse-fade ion-toolbar{--opacity-scale:inherit}.header-collapse-condense{z-index:9}.header-collapse-condense ion-toolbar{position:-webkit-sticky;position:sticky;top:0}.header-collapse-condense ion-toolbar:first-of-type{padding-top:0px;z-index:1}.header-collapse-condense ion-toolbar{--background:var(--ion-background-color, #fff);z-index:0}.header-collapse-condense ion-toolbar:last-of-type{--border-width:0px}.header-collapse-condense ion-toolbar ion-searchbar{padding-top:0px;padding-bottom:13px}.header-collapse-main{--opacity-scale:1}.header-collapse-main ion-toolbar{--opacity-scale:inherit}.header-collapse-main ion-toolbar.in-toolbar ion-title,.header-collapse-main ion-toolbar.in-toolbar ion-buttons{-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse{opacity:0;pointer-events:none}.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-buttons.buttons-collapse{visibility:hidden}ion-header.header-ios:not(.header-collapse-main):has(~ion-content ion-header.header-ios[collapse=condense],~ion-content ion-header.header-ios.header-collapse-condense){opacity:0}";
944
+ const headerIosCss = "ion-header{display:block;position:relative;-ms-flex-order:-1;order:-1;width:100%;z-index:10}ion-header ion-toolbar:first-of-type{padding-top:var(--ion-safe-area-top, 0)}.header-ios ion-toolbar:last-of-type{--border-width:0 0 0.55px}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){.header-background{left:0;right:0;top:0;bottom:0;position:absolute;-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}.header-translucent-ios ion-toolbar{--opacity:.8}.header-collapse-condense-inactive .header-background{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}}.header-ios.ion-no-border ion-toolbar:last-of-type{--border-width:0}.header-collapse-fade ion-toolbar{--opacity-scale:inherit}.header-collapse-fade.header-transitioning ion-toolbar{--background:transparent;--border-style:none}.header-collapse-condense{z-index:9}.header-collapse-condense ion-toolbar{position:-webkit-sticky;position:sticky;top:0}.header-collapse-condense ion-toolbar:first-of-type{padding-top:0px;z-index:1}.header-collapse-condense ion-toolbar{z-index:0}.header-collapse-condense ion-toolbar:last-of-type{--border-width:0px}.header-collapse-condense ion-toolbar ion-searchbar{padding-top:0px;padding-bottom:13px}.header-collapse-main{--opacity-scale:1}.header-collapse-main ion-toolbar{--opacity-scale:inherit}.header-collapse-main ion-toolbar.in-toolbar ion-title,.header-collapse-main ion-toolbar.in-toolbar ion-buttons{-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.header-collapse-condense ion-toolbar,.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar{--background:var(--ion-background-color, #fff)}.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar{--border-style:none;--opacity-scale:1}.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse{opacity:0;pointer-events:none}.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-buttons.buttons-collapse{visibility:hidden}ion-header.header-ios:not(.header-collapse-main):has(~ion-content ion-header.header-ios[collapse=condense],~ion-content ion-header.header-ios.header-collapse-condense){opacity:0}";
908
945
 
909
946
  const headerMdCss = "ion-header{display:block;position:relative;-ms-flex-order:-1;order:-1;width:100%;z-index:10}ion-header ion-toolbar:first-of-type{padding-top:var(--ion-safe-area-top, 0)}.header-md{-webkit-box-shadow:0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);box-shadow:0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12)}.header-collapse-condense{display:none}.header-md.ion-no-border{-webkit-box-shadow:none;box-shadow:none}";
910
947
 
@@ -1044,16 +1081,17 @@ const Header = class {
1044
1081
  const { translucent, inheritedAttributes } = this;
1045
1082
  const mode = ionicGlobal.getIonMode(this);
1046
1083
  const collapse = this.collapse || 'none';
1084
+ const isCondensed = collapse === 'condense';
1047
1085
  // banner role must be at top level, so remove role if inside a menu
1048
- const roleType = theme.hostContext('ion-menu', this.el) ? 'none' : 'banner';
1049
- return (index.h(index.Host, Object.assign({ key: 'b6cc27f0b08afc9fcc889683525da765d80ba672', role: roleType, class: {
1086
+ const roleType = getRoleType(theme.hostContext('ion-menu', this.el), isCondensed, mode);
1087
+ return (index.h(index.Host, Object.assign({ key: '863c4568cd7b8c0ec55109f193bbbaed68a1346e', role: roleType, class: {
1050
1088
  [mode]: true,
1051
1089
  // Used internally for styling
1052
1090
  [`header-${mode}`]: true,
1053
1091
  [`header-translucent`]: this.translucent,
1054
1092
  [`header-collapse-${collapse}`]: true,
1055
1093
  [`header-translucent-${mode}`]: this.translucent,
1056
- } }, inheritedAttributes), mode === 'ios' && translucent && index.h("div", { key: '395766d4dcee3398bc91960db21f922095292f14', class: "header-background" }), index.h("slot", { key: '09a67ece27b258ff1248805d43d92a49b2c6859a' })));
1094
+ } }, inheritedAttributes), mode === 'ios' && translucent && index.h("div", { key: '25c3bdce328b0b35607d154c8b8374679313d881', class: "header-background" }), index.h("slot", { key: 'b44fab0a9be7920b9650da26117c783e751e1702' })));
1057
1095
  }
1058
1096
  get el() { return index.getElement(this); }
1059
1097
  };
@@ -215,11 +215,7 @@ const Button = class {
215
215
  target,
216
216
  };
217
217
  let fill = this.fill;
218
- /**
219
- * We check both undefined and null to
220
- * work around https://github.com/ionic-team/stencil/issues/3586.
221
- */
222
- if (fill == null) {
218
+ if (fill === undefined) {
223
219
  fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
224
220
  }
225
221
  /**
@@ -232,7 +228,7 @@ const Button = class {
232
228
  {
233
229
  type !== 'button' && this.renderHiddenButton();
234
230
  }
235
- return (index.h(index.Host, { key: 'b105ad09215adb3ca2298acdadf0dc9154bbb9b0', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: theme.createColorClasses(color, {
231
+ return (index.h(index.Host, { key: 'ed82ea53705523f9afc5f1a9addff44cc6424f27', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: theme.createColorClasses(color, {
236
232
  [mode]: true,
237
233
  [buttonType]: true,
238
234
  [`${buttonType}-${expand}`]: expand !== undefined,
@@ -247,7 +243,7 @@ const Button = class {
247
243
  'button-disabled': disabled,
248
244
  'ion-activatable': true,
249
245
  'ion-focusable': true,
250
- }) }, index.h(TagType, Object.assign({ key: '66b4e7112bcb9e41d5a723fbbadb0a3104f9ee1d' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), index.h("span", { key: '1439fc3da280221028dcf7ce8ec9dab273c4d4bb', class: "button-inner" }, index.h("slot", { key: 'd5269ae1afc87ec7b99746032f59cbae93720a9f', name: "icon-only", onSlotchange: this.slotChanged }), index.h("slot", { key: '461c83e97aa246aa86d83e14f1e15a288d35041e', name: "start" }), index.h("slot", { key: '807170d47101f9f6a333dd4ff489c89284f306fe' }), index.h("slot", { key: 'e67f116dd0349a0d27893e4f3ff0ccef1d402f80', name: "end" })), mode === 'md' && index.h("ion-ripple-effect", { key: '273f0bd9645a36c1bfd18a5c2ab4f81e22b7b989', type: this.rippleType }))));
246
+ }) }, index.h(TagType, Object.assign({ key: 'fadec13053469dd0405bbbc61b70ced568aa4826' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), index.h("span", { key: '6bf0e5144fb1148002e88038522402b789689d2c', class: "button-inner" }, index.h("slot", { key: '25da0ca155cfa9e2754842c34f4fd09f576ac2d2', name: "icon-only", onSlotchange: this.slotChanged }), index.h("slot", { key: '51414065bb11953ec9d818f8d9353589bc9072c5', name: "start" }), index.h("slot", { key: 'c9b5f8842aeabd20628df2f4600f1257ea913d8d' }), index.h("slot", { key: '478dd3671c7be1909fc84e672f0fa8dfe6082263', name: "end" })), mode === 'md' && index.h("ion-ripple-effect", { key: 'e1d55f85a55144d743f58a5914cd116cb065fa8c', type: this.rippleType }))));
251
247
  }
252
248
  get el() { return index.getElement(this); }
253
249
  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 theme = require('./theme-CeDs6Hcv.js');
9
10
  var ionicGlobal = require('./ionic-global-HMVqOFGO.js');
10
11
 
@@ -61,6 +62,10 @@ const Checkbox = class {
61
62
  * submitting if the value is invalid.
62
63
  */
63
64
  this.required = false;
65
+ /**
66
+ * Track validation state for proper aria-live announcements.
67
+ */
68
+ this.isInvalid = false;
64
69
  /**
65
70
  * Sets the checked property and emits
66
71
  * the ionChange event. Use this to update the
@@ -76,7 +81,6 @@ const Checkbox = class {
76
81
  };
77
82
  this.toggleChecked = (ev) => {
78
83
  ev.preventDefault();
79
- this.setFocus();
80
84
  this.setChecked(!this.checked);
81
85
  this.indeterminate = false;
82
86
  };
@@ -108,18 +112,63 @@ const Checkbox = class {
108
112
  ev.stopPropagation();
109
113
  };
110
114
  }
115
+ connectedCallback() {
116
+ const { el } = this;
117
+ // Watch for class changes to update validation state.
118
+ if (typeof MutationObserver !== 'undefined') {
119
+ this.validationObserver = new MutationObserver(() => {
120
+ const newIsInvalid = validity.checkInvalidState(el);
121
+ if (this.isInvalid !== newIsInvalid) {
122
+ this.isInvalid = newIsInvalid;
123
+ /**
124
+ * Screen readers tend to announce changes
125
+ * to `aria-describedby` when the attribute
126
+ * is changed during a blur event for a
127
+ * native form control.
128
+ * However, the announcement can be spotty
129
+ * when using a non-native form control
130
+ * and `forceUpdate()`.
131
+ * This is due to `forceUpdate()` internally
132
+ * rescheduling the DOM update to a lower
133
+ * priority queue regardless if it's called
134
+ * inside a Promise or not, thus causing
135
+ * the screen reader to potentially miss the
136
+ * change.
137
+ * By using a State variable inside a Promise,
138
+ * it guarantees a re-render immediately at
139
+ * a higher priority.
140
+ */
141
+ Promise.resolve().then(() => {
142
+ this.hintTextId = this.getHintTextId();
143
+ });
144
+ }
145
+ });
146
+ this.validationObserver.observe(el, {
147
+ attributes: true,
148
+ attributeFilter: ['class'],
149
+ });
150
+ }
151
+ // Always set initial state
152
+ this.isInvalid = validity.checkInvalidState(el);
153
+ }
111
154
  componentWillLoad() {
112
155
  this.inheritedAttributes = Object.assign({}, helpers.inheritAriaAttributes(this.el));
156
+ this.hintTextId = this.getHintTextId();
157
+ }
158
+ disconnectedCallback() {
159
+ // Clean up validation observer to prevent memory leaks.
160
+ if (this.validationObserver) {
161
+ this.validationObserver.disconnect();
162
+ this.validationObserver = undefined;
163
+ }
113
164
  }
114
165
  /** @internal */
115
166
  async setFocus() {
116
- if (this.focusEl) {
117
- this.focusEl.focus();
118
- }
167
+ this.el.focus();
119
168
  }
120
- getHintTextID() {
121
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
122
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
169
+ getHintTextId() {
170
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
171
+ if (isInvalid && errorText) {
123
172
  return errorTextId;
124
173
  }
125
174
  if (helperText) {
@@ -132,7 +181,7 @@ const Checkbox = class {
132
181
  * This element should only be rendered if hint text is set.
133
182
  */
134
183
  renderHintText() {
135
- const { helperText, errorText, helperTextId, errorTextId } = this;
184
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
136
185
  /**
137
186
  * undefined and empty string values should
138
187
  * be treated as not having helper/error text.
@@ -141,7 +190,7 @@ const Checkbox = class {
141
190
  if (!hasHintText) {
142
191
  return;
143
192
  }
144
- return (index.h("div", { class: "checkbox-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)));
193
+ return (index.h("div", { class: "checkbox-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)));
145
194
  }
146
195
  render() {
147
196
  const { color, checked, disabled, el, getSVGPath, indeterminate, inheritedAttributes, inputId, justify, labelPlacement, name, value, alignment, required, } = this;
@@ -151,7 +200,7 @@ const Checkbox = class {
151
200
  helpers.renderHiddenInput(true, el, name, checked ? value : '', disabled);
152
201
  // The host element must have a checkbox role to ensure proper VoiceOver
153
202
  // support in Safari for accessibility.
154
- return (index.h(index.Host, { key: '26cbe7220e555107200e9b5deeae754aa534a80b', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, "aria-labelledby": hasLabelContent ? this.inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, class: theme.createColorClasses(color, {
203
+ return (index.h(index.Host, { key: 'ae0fbd4b21accbac132e6b85c513512ad9179394', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": hasLabelContent ? this.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, onClick: this.onClick, class: theme.createColorClasses(color, {
155
204
  [mode]: true,
156
205
  'in-item': theme.hostContext('ion-item', el),
157
206
  'checkbox-checked': checked,
@@ -161,10 +210,10 @@ const Checkbox = class {
161
210
  [`checkbox-justify-${justify}`]: justify !== undefined,
162
211
  [`checkbox-alignment-${alignment}`]: alignment !== undefined,
163
212
  [`checkbox-label-placement-${labelPlacement}`]: true,
164
- }), onClick: this.onClick }, index.h("label", { key: 'f025cec5ff08e8be4487b9cc0324616ca5dfae2a', class: "checkbox-wrapper", htmlFor: inputId }, index.h("input", Object.assign({ key: 'dc53f7e4e240dc2e18556e6350df2b5c3169f553', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, onFocus: () => this.onFocus(), onBlur: () => this.onBlur(), ref: (focusEl) => (this.focusEl = focusEl), required: required }, inheritedAttributes)), index.h("div", { key: 'a625e9b50c3b617de8bbbfd624d772454fecaf2d', class: {
213
+ }) }, index.h("label", { key: '7a3d7f3c27dde514f2dbf2e34f4629fad33ec3bf', class: "checkbox-wrapper", htmlFor: inputId }, index.h("input", Object.assign({ key: '4130d77ddf034271fecccda14e101a5a809921b6', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), index.h("div", { key: '5daa74f4e62b0947e37764762524001ee42609d9', class: {
165
214
  'label-text-wrapper': true,
166
215
  'label-text-wrapper-hidden': !hasLabelContent,
167
- }, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, index.h("slot", { key: '87d1a90691327945f4343406706e4ab27f453844' }), this.renderHintText()), index.h("div", { key: 'b57fed8cdecee4df1ef0d57f157267ee77fac653', class: "native-wrapper" }, index.h("svg", { key: '13a8aac044d46dc99e3b60a1a643785511f216ac', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
216
+ }, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, index.h("slot", { key: '23ff66138f8c3a2f56f39113fc842d54b2f7952a' }), this.renderHintText()), index.h("div", { key: 'ab914d9623c19fc46821d5e62db92f1192ebbe7e', class: "native-wrapper" }, index.h("svg", { key: '66e3f4f5dcaa9756fb0e9452299954f9ed3dcb7b', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
168
217
  }
169
218
  getSVGPath(mode, indeterminate) {
170
219
  let path = indeterminate ? (index.h("path", { d: "M6 12L18 12", part: "mark" })) : (index.h("path", { d: "M5.9,12.5l3.8,3.8l8.8-8.8", part: "mark" }));