voyager-ionic-core 8.8.12 → 8.8.17

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 (237) hide show
  1. package/components/index.js +1 -1
  2. package/components/ion-accordion.js +1 -1
  3. package/components/ion-action-sheet.js +1 -1
  4. package/components/ion-alert.js +1 -1
  5. package/components/ion-app.js +1 -1
  6. package/components/ion-back-button.js +1 -1
  7. package/components/ion-breadcrumb.js +1 -1
  8. package/components/ion-button.js +1 -1
  9. package/components/ion-card.js +1 -1
  10. package/components/ion-checkbox.js +1 -1
  11. package/components/ion-content.js +1 -1
  12. package/components/ion-datetime-button.js +1 -1
  13. package/components/ion-datetime.js +1 -1
  14. package/components/ion-fab-button.js +1 -1
  15. package/components/ion-footer.js +1 -1
  16. package/components/ion-header.js +1 -1
  17. package/components/ion-img.js +1 -1
  18. package/components/ion-infinite-scroll.js +1 -1
  19. package/components/ion-input-otp.js +1 -1
  20. package/components/ion-input-password-toggle.js +1 -1
  21. package/components/ion-input.js +1 -1
  22. package/components/ion-item-options.js +1 -1
  23. package/components/ion-item-sliding.js +1 -1
  24. package/components/ion-item.js +1 -1
  25. package/components/ion-loading.js +1 -1
  26. package/components/ion-menu-button.js +1 -1
  27. package/components/ion-menu-toggle.js +1 -1
  28. package/components/ion-menu.js +1 -1
  29. package/components/ion-modal.js +1 -1
  30. package/components/ion-nav.js +1 -1
  31. package/components/ion-picker-column-option.js +1 -1
  32. package/components/ion-picker-column.js +1 -1
  33. package/components/ion-picker-legacy-column.js +1 -1
  34. package/components/ion-picker-legacy.js +1 -1
  35. package/components/ion-picker.js +1 -1
  36. package/components/ion-popover.js +1 -1
  37. package/components/ion-progress-bar.js +1 -1
  38. package/components/ion-radio-group.js +1 -1
  39. package/components/ion-radio.js +1 -1
  40. package/components/ion-range.js +1 -1
  41. package/components/ion-refresher-content.js +1 -1
  42. package/components/ion-refresher.js +1 -1
  43. package/components/ion-reorder-group.js +1 -1
  44. package/components/ion-router-outlet.js +1 -1
  45. package/components/ion-router.js +1 -1
  46. package/components/ion-searchbar.js +1 -1
  47. package/components/ion-segment-button.js +1 -1
  48. package/components/ion-segment.js +1 -1
  49. package/components/ion-select-modal.js +1 -1
  50. package/components/ion-select-popover.js +1 -1
  51. package/components/ion-select.js +1 -1
  52. package/components/ion-tab-button.js +1 -1
  53. package/components/ion-tab.js +1 -1
  54. package/components/ion-textarea.js +1 -1
  55. package/components/ion-toast.js +1 -1
  56. package/components/ion-toggle.js +1 -1
  57. package/components/{p-CtukzcyX.js → p-A3O24YmY.js} +1 -1
  58. package/components/{p-CU5rbWAW.js → p-ABkNVeU9.js} +1 -1
  59. package/components/{p-BT_5jSqG.js → p-B-JJgYSG.js} +1 -1
  60. package/components/p-BB42Mlnr.js +4 -0
  61. package/components/{p-lE_JGpHD.js → p-BMI53wYL.js} +1 -1
  62. package/components/p-BkKAANMJ.js +4 -0
  63. package/components/p-BlIAuGv2.js +4 -0
  64. package/components/{p-Bth-EzJ0.js → p-Bnsn6KaP.js} +1 -1
  65. package/components/p-BsBCoGa-.js +4 -0
  66. package/components/{p-BnqQlm7c.js → p-CQGnMY3n.js} +1 -1
  67. package/components/{p-DNQI6d-L.js → p-C_hpsif8.js} +1 -1
  68. package/components/p-Cf2jSDS8.js +4 -0
  69. package/components/{p-C4t5ymfq.js → p-ChIZKXUu.js} +1 -1
  70. package/components/p-CkMSOsFF.js +4 -0
  71. package/components/p-CsNJ1ULL.js +4 -0
  72. package/components/{p-BsPelv04.js → p-CzCbeKys.js} +1 -1
  73. package/components/{p-BjfgFSSR.js → p-D-aHdMxW.js} +1 -1
  74. package/components/p-D0d8qTwj.js +4 -0
  75. package/components/{p-Baq1XyAy.js → p-D3xo7hqU.js} +1 -1
  76. package/components/{p-nQi4oKcG.js → p-DCnGU1fF.js} +1 -1
  77. package/components/p-DGZpx6KJ.js +4 -0
  78. package/components/p-DGvrK8dD.js +4 -0
  79. package/components/p-DHTSTb52.js +4 -0
  80. package/components/{p-BirPFsRD.js → p-DJZp0KTQ.js} +1 -1
  81. package/components/{p-ijF0iCrA.js → p-DRugphUN.js} +1 -1
  82. package/components/{p-0NRupjzW.js → p-DV5mPYQS.js} +1 -1
  83. package/components/{p-C1aScSTo.js → p-DWkAgceV.js} +1 -1
  84. package/components/{p-DDWp95gH.js → p-KEFO0w3i.js} +1 -1
  85. package/components/{p-Ciz1-FuE.js → p-Osve4qkc.js} +1 -1
  86. package/components/{p-dhLYXa7A.js → p-SLS1rDZM.js} +1 -1
  87. package/components/{p-D5Ubpm7D.js → p-VWbRkLT7.js} +1 -1
  88. package/components/{p-BTPfOxsU.js → p-aJ6PBUvj.js} +1 -1
  89. package/components/{p-DnL1c9_X.js → p-oD4kcBRX.js} +1 -1
  90. package/components/{p-CI3ncNZm.js → p-scxfI1Dd.js} +1 -1
  91. package/components/p-yzCsOZE2.js +4 -0
  92. package/dist/cjs/{data-DLTUw-KF.js → format-PxvGiOik.js} +8 -532
  93. package/dist/cjs/index.cjs.js +1 -1
  94. package/dist/cjs/ion-action-sheet.cjs.entry.js +1 -1
  95. package/dist/cjs/ion-alert.cjs.entry.js +1 -1
  96. package/dist/cjs/ion-button_2.cjs.entry.js +7 -3
  97. package/dist/cjs/ion-checkbox.cjs.entry.js +17 -7
  98. package/dist/cjs/ion-datetime-button.cjs.entry.js +17 -15
  99. package/dist/cjs/ion-datetime_3.cjs.entry.js +616 -75
  100. package/dist/cjs/ion-fab_3.cjs.entry.js +87 -9
  101. package/dist/cjs/ion-item_8.cjs.entry.js +84 -4
  102. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  103. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  104. package/dist/cjs/ion-modal.cjs.entry.js +35 -9
  105. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  106. package/dist/cjs/ion-radio_2.cjs.entry.js +16 -7
  107. package/dist/cjs/ion-refresher_2.cjs.entry.js +31 -4
  108. package/dist/cjs/ion-select-modal.cjs.entry.js +1 -1
  109. package/dist/cjs/ion-select_3.cjs.entry.js +5 -4
  110. package/dist/cjs/ion-tab_2.cjs.entry.js +20 -13
  111. package/dist/cjs/ion-toast.cjs.entry.js +1 -1
  112. package/dist/cjs/ion-toggle.cjs.entry.js +25 -6
  113. package/dist/cjs/ionic.cjs.js +1 -1
  114. package/dist/cjs/item-multiple-inputs-D-gyg57c.js +38 -0
  115. package/dist/cjs/loader.cjs.js +1 -1
  116. package/dist/cjs/{overlays-TbKsuC-K.js → overlays-DdtIYQUH.js} +24 -1
  117. package/dist/collection/components/button/button.js +7 -3
  118. package/dist/collection/components/checkbox/checkbox.ios.css +18 -0
  119. package/dist/collection/components/checkbox/checkbox.js +16 -7
  120. package/dist/collection/components/checkbox/checkbox.md.css +18 -0
  121. package/dist/collection/components/datetime/datetime.js +51 -6
  122. package/dist/collection/components/datetime-button/datetime-button.js +8 -7
  123. package/dist/collection/components/fab-button/fab-button.js +114 -10
  124. package/dist/collection/components/item/item.ios.css +11 -0
  125. package/dist/collection/components/item/item.js +85 -4
  126. package/dist/collection/components/item/item.md.css +11 -0
  127. package/dist/collection/components/modal/gestures/sheet.js +7 -0
  128. package/dist/collection/components/modal/gestures/swipe-to-close.js +7 -0
  129. package/dist/collection/components/modal/modal.ios.css +8 -0
  130. package/dist/collection/components/modal/modal.js +18 -6
  131. package/dist/collection/components/modal/modal.md.css +8 -0
  132. package/dist/collection/components/radio/radio.ios.css +11 -15
  133. package/dist/collection/components/radio/radio.js +15 -7
  134. package/dist/collection/components/radio/radio.md.css +11 -11
  135. package/dist/collection/components/refresher/refresher.js +31 -4
  136. package/dist/collection/components/select/select.js +4 -3
  137. package/dist/collection/components/tab/tab.js +20 -13
  138. package/dist/collection/components/toggle/toggle.ios.css +12 -4
  139. package/dist/collection/components/toggle/toggle.js +24 -6
  140. package/dist/collection/components/toggle/toggle.md.css +42 -5
  141. package/dist/collection/utils/forms/index.js +1 -0
  142. package/dist/collection/utils/forms/item-multiple-inputs.js +33 -0
  143. package/dist/collection/utils/overlays.js +24 -1
  144. package/dist/collection/utils/test/playwright/apply-keyboard-focus.js +25 -0
  145. package/dist/collection/utils/test/playwright/drag-element.js +15 -0
  146. package/dist/collection/utils/test/playwright/index.js +1 -0
  147. package/dist/docs.json +40 -1
  148. package/dist/esm/{data-DZI70dKr.js → format-CAmvpAez.js} +1 -524
  149. package/dist/esm/index.js +1 -1
  150. package/dist/esm/ion-action-sheet.entry.js +1 -1
  151. package/dist/esm/ion-alert.entry.js +1 -1
  152. package/dist/esm/ion-button_2.entry.js +7 -3
  153. package/dist/esm/ion-checkbox.entry.js +19 -9
  154. package/dist/esm/ion-datetime-button.entry.js +9 -7
  155. package/dist/esm/ion-datetime_3.entry.js +550 -9
  156. package/dist/esm/ion-fab_3.entry.js +89 -11
  157. package/dist/esm/ion-item_8.entry.js +84 -4
  158. package/dist/esm/ion-loading.entry.js +1 -1
  159. package/dist/esm/ion-menu_3.entry.js +1 -1
  160. package/dist/esm/ion-modal.entry.js +35 -9
  161. package/dist/esm/ion-popover.entry.js +1 -1
  162. package/dist/esm/ion-radio_2.entry.js +17 -8
  163. package/dist/esm/ion-refresher_2.entry.js +31 -4
  164. package/dist/esm/ion-select-modal.entry.js +1 -1
  165. package/dist/esm/ion-select_3.entry.js +5 -4
  166. package/dist/esm/ion-tab_2.entry.js +20 -13
  167. package/dist/esm/ion-toast.entry.js +1 -1
  168. package/dist/esm/ion-toggle.entry.js +27 -8
  169. package/dist/esm/ionic.js +1 -1
  170. package/dist/esm/item-multiple-inputs-DlNuqNJm.js +36 -0
  171. package/dist/esm/loader.js +1 -1
  172. package/dist/esm/{overlays-TbiM4mdr.js → overlays-Dg0q2_tJ.js} +25 -2
  173. package/dist/html.html-data.json +4 -0
  174. package/dist/ionic/index.esm.js +1 -1
  175. package/dist/ionic/ionic.esm.js +1 -1
  176. package/dist/ionic/{p-30bf9737.entry.js → p-0dc33652.entry.js} +1 -1
  177. package/dist/ionic/p-10e833ef.entry.js +4 -0
  178. package/dist/ionic/{p-4dc1c33c.entry.js → p-2a05aa64.entry.js} +1 -1
  179. package/dist/ionic/p-2b204c59.entry.js +4 -0
  180. package/dist/ionic/p-2fa58e80.entry.js +4 -0
  181. package/dist/ionic/{p-94f2a736.entry.js → p-34586ead.entry.js} +1 -1
  182. package/dist/ionic/p-4654ae1c.entry.js +4 -0
  183. package/dist/ionic/p-4ac4f3d6.entry.js +4 -0
  184. package/dist/ionic/p-5018dfc8.entry.js +4 -0
  185. package/dist/ionic/{p-00dbb17e.entry.js → p-5eb8e86d.entry.js} +1 -1
  186. package/dist/ionic/{p-e847a888.entry.js → p-65ebdf1b.entry.js} +1 -1
  187. package/dist/ionic/p-7c319341.entry.js +4 -0
  188. package/dist/ionic/p-829367ed.entry.js +4 -0
  189. package/dist/ionic/{p-aa5d6b8f.entry.js → p-9f9db3a2.entry.js} +1 -1
  190. package/dist/ionic/p-C0eMdTCW.js +4 -0
  191. package/dist/ionic/p-D-3ycHmd.js +4 -0
  192. package/dist/ionic/p-DqN5EA2O.js +4 -0
  193. package/dist/ionic/p-aaeedb6b.entry.js +4 -0
  194. package/dist/ionic/p-bfc9bb0f.entry.js +4 -0
  195. package/dist/ionic/p-c38b648e.entry.js +4 -0
  196. package/dist/ionic/{p-3a541154.entry.js → p-ca2ab74d.entry.js} +1 -1
  197. package/dist/ionic/p-d889ff6d.entry.js +4 -0
  198. package/dist/types/components/checkbox/checkbox.d.ts +1 -0
  199. package/dist/types/components/datetime/datetime.d.ts +8 -0
  200. package/dist/types/components/fab-button/fab-button.d.ts +15 -0
  201. package/dist/types/components/item/item.d.ts +19 -0
  202. package/dist/types/components/radio/radio.d.ts +1 -0
  203. package/dist/types/components/tab/tab.d.ts +1 -1
  204. package/dist/types/components/toggle/toggle.d.ts +1 -0
  205. package/dist/types/components.d.ts +15 -2
  206. package/dist/types/utils/forms/index.d.ts +1 -0
  207. package/dist/types/utils/forms/item-multiple-inputs.d.ts +13 -0
  208. package/dist/types/utils/test/playwright/apply-keyboard-focus.d.ts +16 -0
  209. package/dist/types/utils/test/playwright/index.d.ts +1 -0
  210. package/hydrate/index.js +382 -79
  211. package/hydrate/index.mjs +382 -79
  212. package/package.json +5 -5
  213. package/components/p-5bE_Wz4Y.js +0 -4
  214. package/components/p-B60hm8Qv.js +0 -4
  215. package/components/p-BAYYcg3o.js +0 -4
  216. package/components/p-BjoEPDW5.js +0 -4
  217. package/components/p-C2lKgrXx.js +0 -4
  218. package/components/p-D7bSXQD3.js +0 -4
  219. package/components/p-DIE4pXMl.js +0 -4
  220. package/components/p-W5nO8mX_.js +0 -4
  221. package/components/p-W9CZ2mWX.js +0 -4
  222. package/components/p-XlSIJDJ-.js +0 -4
  223. package/components/p-ryqmO0B-.js +0 -4
  224. package/dist/ionic/p-0d005398.entry.js +0 -4
  225. package/dist/ionic/p-1ae21e4c.entry.js +0 -4
  226. package/dist/ionic/p-2f55dc1a.entry.js +0 -4
  227. package/dist/ionic/p-3a126bca.entry.js +0 -4
  228. package/dist/ionic/p-3a7d1e91.entry.js +0 -4
  229. package/dist/ionic/p-81ea5652.entry.js +0 -4
  230. package/dist/ionic/p-8c8eee7d.entry.js +0 -4
  231. package/dist/ionic/p-9a65ac7f.entry.js +0 -4
  232. package/dist/ionic/p-DAAFkPFT.js +0 -4
  233. package/dist/ionic/p-a21add2f.entry.js +0 -4
  234. package/dist/ionic/p-ec090e0a.entry.js +0 -4
  235. package/dist/ionic/p-fabcce6c.entry.js +0 -4
  236. package/dist/ionic/p-facfde02.entry.js +0 -4
  237. package/dist/ionic/p-xOfi7-Zn.js +0 -4
@@ -1,8 +1,8 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { Build, Host, h } from "@stencil/core";
5
- import { checkInvalidState } from "../../utils/forms/index";
4
+ import { Build, Host, forceUpdate, h } from "@stencil/core";
5
+ import { checkInvalidState, createItemMultipleInputsObserver } from "../../utils/forms/index";
6
6
  import { inheritAriaAttributes, renderHiddenInput } from "../../utils/helpers";
7
7
  import { createColorClasses, hostContext } from "../../utils/theme";
8
8
  import { getIonMode } from "../../global/ionic-global";
@@ -162,17 +162,21 @@ export class Checkbox {
162
162
  // Always set initial state
163
163
  this.isInvalid = checkInvalidState(el);
164
164
  this.hasLabelContent = this.el.textContent !== '';
165
+ this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this));
165
166
  }
166
167
  componentWillLoad() {
167
168
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
168
169
  this.hintTextId = this.getHintTextId();
169
170
  }
170
171
  disconnectedCallback() {
171
- // Clean up validation observer to prevent memory leaks.
172
172
  if (this.validationObserver) {
173
173
  this.validationObserver.disconnect();
174
174
  this.validationObserver = undefined;
175
175
  }
176
+ if (this.itemFocusObserver) {
177
+ this.itemFocusObserver.disconnect();
178
+ this.itemFocusObserver = undefined;
179
+ }
176
180
  }
177
181
  /** @internal */
178
182
  async setFocus() {
@@ -208,23 +212,28 @@ export class Checkbox {
208
212
  const { color, checked, disabled, el, getSVGPath, indeterminate, inheritedAttributes, inputId, justify, labelPlacement, name, value, alignment, required, } = this;
209
213
  const mode = getIonMode(this);
210
214
  const path = getSVGPath(mode, indeterminate);
215
+ const inItem = hostContext('ion-item', el);
216
+ const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el);
211
217
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
212
218
  // The host element must have a checkbox role to ensure proper VoiceOver
213
219
  // support in Safari for accessibility.
214
- return (h(Host, { key: '0da370f94c5cdf3b08bc9008395558334a300f35', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": this.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: createColorClasses(color, {
220
+ return (h(Host, { key: '6de0a71ffc45e65a4cf5ab0a1216f3ccd159d8de', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": this.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: createColorClasses(color, {
215
221
  [mode]: true,
216
- 'in-item': hostContext('ion-item', el),
222
+ 'in-item': inItem,
217
223
  'checkbox-checked': checked,
218
224
  'checkbox-disabled': disabled,
219
225
  'checkbox-indeterminate': indeterminate,
220
226
  interactive: true,
227
+ // A single-input item has the input cover and draws the indicator itself.
228
+ // A multi-input item has no cover, so each control draws its own.
229
+ 'ion-focusable': !inItem || inMultipleInputsItem,
221
230
  [`checkbox-justify-${justify}`]: justify !== undefined,
222
231
  [`checkbox-alignment-${alignment}`]: alignment !== undefined,
223
232
  [`checkbox-label-placement-${labelPlacement}`]: true,
224
- }) }, h("label", { key: '991f1763356671230af119a5fbdc22d0a39974e7', class: "checkbox-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: '982f8a7f84d013b272b17607936355d2b6c251f4', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), h("div", { key: 'c8f9e8baa20ac68e69fd3c6fcf0e7a26a1084d83', class: {
233
+ }) }, h("label", { key: 'acca2f5b0a37aa70930b2f74ff4857939b2be2b0', class: "checkbox-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: 'f46e409383e9d3ffc3daae8e7cd4159427d4c8fe', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), h("div", { key: '69d8bf21c781cd9b1f56ceae40ff7911e6f5adfe', class: {
225
234
  'label-text-wrapper': true,
226
235
  'label-text-wrapper-hidden': !this.hasLabelContent,
227
- }, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '6018205e0a73dec826c7881d687f1c2ca8dcb0ab' }), this.renderHintText()), h("div", { key: '57530b9d6ff59ee7ab98f960cd65d66ee87cfd1d', class: "native-wrapper" }, h("svg", { key: '63d719154ff44459e9ca448e3f5d7de94d9ab248', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
236
+ }, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '3c444ab897456c522588fc6cee616030f16b0586' }), this.renderHintText()), h("div", { key: '0a18dfe7a68a8eec08ce8764bd2c8a59c4a38802', class: "native-wrapper" }, h("svg", { key: '56b298b54ebc88a429a9dd67b8dabd7063759b3a', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
228
237
  }
229
238
  getSVGPath(mode, indeterminate) {
230
239
  let path = indeterminate ? (h("path", { d: "M6 12L18 12", part: "mark" })) : (h("path", { d: "M5.9,12.5l3.8,3.8l8.8-8.8", part: "mark" }));
@@ -153,8 +153,13 @@ input {
153
153
  display: none;
154
154
  }
155
155
 
156
+ :host(:focus) {
157
+ outline: none;
158
+ }
159
+
156
160
  .native-wrapper {
157
161
  display: flex;
162
+ position: relative;
158
163
  align-items: center;
159
164
  }
160
165
 
@@ -178,6 +183,19 @@ input {
178
183
  opacity: 0;
179
184
  }
180
185
 
186
+ :host(.ion-focused) .native-wrapper::after {
187
+ border-radius: 50%;
188
+ display: block;
189
+ position: absolute;
190
+ width: 36px;
191
+ height: 36px;
192
+ background: var(--ion-color-primary-tint, #1a65eb);
193
+ content: "";
194
+ opacity: 0.2;
195
+ inset-block-start: calc(50% - 36px / 2);
196
+ inset-inline-start: calc(50% - 36px / 2);
197
+ }
198
+
181
199
  .checkbox-bottom {
182
200
  padding-top: 4px;
183
201
  display: flex;
@@ -9,6 +9,7 @@ import { FOCUS_TRAP_DISABLE_CLASS } from "../../utils/overlays";
9
9
  import { isRTL } from "../../utils/rtl/index";
10
10
  import { createColorClasses } from "../../utils/theme";
11
11
  import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward } from "ionicons/icons";
12
+ import { config } from "../../global/config";
12
13
  import { getIonMode } from "../../global/ionic-global";
13
14
  import { isSameDay, warnIfValueOutOfBounds, isBefore, isAfter } from "./utils/comparison";
14
15
  import { generateMonths, getDaysOfMonth, getDaysOfWeek, getToday, getMonthColumnData, getDayColumnData, getYearColumnData, getTimeColumnsData, getCombinedDateColumnData, } from "./utils/data";
@@ -780,10 +781,11 @@ export class Datetime {
780
781
  return;
781
782
  }
782
783
  const left = nextMonth.offsetWidth * 2;
784
+ const scrollMode = config.getBoolean('animated', true) ? 'smooth' : 'instant';
783
785
  calendarBodyRef.scrollTo({
784
786
  top: 0,
785
787
  left: left * (isRTL(this.el) ? -1 : 1),
786
- behavior: 'smooth',
788
+ behavior: scrollMode,
787
789
  });
788
790
  };
789
791
  this.prevMonth = () => {
@@ -796,10 +798,11 @@ export class Datetime {
796
798
  return;
797
799
  }
798
800
  const left = prevMonth.offsetWidth * 2;
801
+ const scrollMode = config.getBoolean('animated', true) ? 'smooth' : 'instant';
799
802
  calendarBodyRef.scrollTo({
800
803
  top: 0,
801
804
  left: left * (isRTL(this.el) ? 1 : -1),
802
- behavior: 'smooth',
805
+ behavior: scrollMode,
803
806
  });
804
807
  };
805
808
  this.toggleMonthAndYearView = () => {
@@ -914,6 +917,16 @@ export class Datetime {
914
917
  this.closeParentOverlay(CANCEL_ROLE);
915
918
  }
916
919
  }
920
+ /**
921
+ * Returns the default parts the datetime falls back to when no value is set:
922
+ * today's date and time snapped to the closest value allowed by the
923
+ * component's constraints (`min`, `max`, and the `*Values` props).
924
+ *
925
+ * @internal
926
+ */
927
+ async getDefaultPart() {
928
+ return this.defaultParts;
929
+ }
917
930
  get isCalendarPicker() {
918
931
  const { presentation } = this;
919
932
  return presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
@@ -1326,7 +1339,10 @@ export class Datetime {
1326
1339
  const activePart = this.getActivePartsWithFallback();
1327
1340
  return (h("ion-picker-column", { part: WHEEL_PART, "aria-label": "Select a month", class: "month-column", color: this.color, disabled: disabled, value: workingParts.month, onIonChange: (ev) => {
1328
1341
  this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { month: ev.detail.value }));
1329
- this.setActiveParts(Object.assign(Object.assign({}, activePart), { month: ev.detail.value }));
1342
+ // Month wheel is navigation-only in multi-select mode as a fix for https://github.com/ionic-team/ionic-framework/issues/29673
1343
+ if (!this.multiple) {
1344
+ this.setActiveParts(Object.assign(Object.assign({}, activePart), { month: ev.detail.value }));
1345
+ }
1330
1346
  ev.stopPropagation();
1331
1347
  } }, months.map((month) => (h("ion-picker-column-option", { part: month.value === workingParts.month ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: month.value, disabled: month.disabled, value: month.value }, month.text)))));
1332
1348
  }
@@ -1338,7 +1354,10 @@ export class Datetime {
1338
1354
  const activePart = this.getActivePartsWithFallback();
1339
1355
  return (h("ion-picker-column", { part: WHEEL_PART, "aria-label": "Select a year", class: "year-column", color: this.color, disabled: disabled, value: workingParts.year, onIonChange: (ev) => {
1340
1356
  this.setWorkingParts(Object.assign(Object.assign({}, workingParts), { year: ev.detail.value }));
1341
- this.setActiveParts(Object.assign(Object.assign({}, activePart), { year: ev.detail.value }));
1357
+ // Year wheel is navigation-only in multi-select mode as a fix for https://github.com/ionic-team/ionic-framework/issues/29673
1358
+ if (!this.multiple) {
1359
+ this.setActiveParts(Object.assign(Object.assign({}, activePart), { year: ev.detail.value }));
1360
+ }
1342
1361
  ev.stopPropagation();
1343
1362
  } }, years.map((year) => (h("ion-picker-column-option", { part: year.value === workingParts.year ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART, key: year.value, disabled: year.disabled, value: year.value }, year.text)))));
1344
1363
  }
@@ -1735,7 +1754,7 @@ export class Datetime {
1735
1754
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1736
1755
  const hasWheelVariant = hasDatePresentation && preferWheel;
1737
1756
  renderHiddenInput(true, el, name, formatValue(value), disabled);
1738
- return (h(Host, { key: '323c8c2327088f00934b8c93c3306538cb9b5677', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1757
+ return (h(Host, { key: '5e0b83cb88a87945eaae2f9402751819b75452ee', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1739
1758
  [mode]: true,
1740
1759
  ['datetime-readonly']: readonly,
1741
1760
  ['datetime-disabled']: disabled,
@@ -1745,7 +1764,7 @@ export class Datetime {
1745
1764
  [`datetime-size-${size}`]: true,
1746
1765
  [`datetime-prefer-wheel`]: hasWheelVariant,
1747
1766
  [`datetime-grid`]: isGridStyle,
1748
- })) }, h("div", { key: '1e0855c8909bc3f1e48a21ad68159fa782060691', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1767
+ })) }, h("div", { key: '9f9ba72e9879525961b00abe342a7891492ca0ff', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1749
1768
  }
1750
1769
  static get is() { return "ion-datetime"; }
1751
1770
  static get encapsulation() { return "shadow"; }
@@ -2632,6 +2651,32 @@ export class Datetime {
2632
2651
  "text": "closeOverlay If `true`, closes the parent overlay. Defaults to `false`."
2633
2652
  }]
2634
2653
  }
2654
+ },
2655
+ "getDefaultPart": {
2656
+ "complexType": {
2657
+ "signature": "() => Promise<DatetimeParts>",
2658
+ "parameters": [],
2659
+ "references": {
2660
+ "Promise": {
2661
+ "location": "global",
2662
+ "id": "global::Promise"
2663
+ },
2664
+ "DatetimeParts": {
2665
+ "location": "import",
2666
+ "path": "./datetime-interface",
2667
+ "id": "src/components/datetime/datetime-interface.ts::DatetimeParts",
2668
+ "referenceLocation": "DatetimeParts"
2669
+ }
2670
+ },
2671
+ "return": "Promise<DatetimeParts>"
2672
+ },
2673
+ "docs": {
2674
+ "text": "Returns the default parts the datetime falls back to when no value is set:\ntoday's date and time snapped to the closest value allowed by the\ncomponent's constraints (`min`, `max`, and the `*Values` props).",
2675
+ "tags": [{
2676
+ "name": "internal",
2677
+ "text": undefined
2678
+ }]
2679
+ }
2635
2680
  }
2636
2681
  };
2637
2682
  }
@@ -6,7 +6,6 @@ import { componentOnReady, addEventListener } from "../../utils/helpers";
6
6
  import { printIonError } from "../../utils/logging/index";
7
7
  import { createColorClasses } from "../../utils/theme";
8
8
  import { getIonMode } from "../../global/ionic-global";
9
- import { getToday } from "../datetime/utils/data";
10
9
  import { getLocalizedDateTime, getLocalizedTime } from "../datetime/utils/format";
11
10
  import { getHourCycle } from "../datetime/utils/helpers";
12
11
  import { parseDate } from "../datetime/utils/parse";
@@ -54,7 +53,7 @@ export class DatetimeButton {
54
53
  * ion-datetime and then format it according
55
54
  * to the locale specified on ion-datetime.
56
55
  */
57
- this.setDateTimeText = () => {
56
+ this.setDateTimeText = async () => {
58
57
  var _a, _b, _c, _d, _e;
59
58
  const { datetimeEl, datetimePresentation } = this;
60
59
  if (!datetimeEl) {
@@ -63,10 +62,12 @@ export class DatetimeButton {
63
62
  const { value, locale, formatOptions, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl;
64
63
  const parsedValues = this.getParsedDateValues(value);
65
64
  /**
66
- * Both ion-datetime and ion-datetime-button default
67
- * to today's date and time if no value is set.
65
+ * Both ion-datetime and ion-datetime-button default to today's date and
66
+ * time if no value is set. We read the datetime's computed default so the
67
+ * button respects the same constraints (min, max, minuteValues, etc.) that
68
+ * the datetime applies to its own fallback, instead of using a raw "now".
68
69
  */
69
- const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : [getToday()]);
70
+ const parsedDatetimes = parsedValues.length > 0 ? parseDate(parsedValues) : [await datetimeEl.getDefaultPart()];
70
71
  if (!parsedDatetimes) {
71
72
  return;
72
73
  }
@@ -341,11 +342,11 @@ export class DatetimeButton {
341
342
  render() {
342
343
  const { color, dateText, timeText, selectedButton, datetimeActive, disabled } = this;
343
344
  const mode = getIonMode(this);
344
- return (h(Host, { key: '11d037e6ab061e5116842970760b04850b42f2c7', class: createColorClasses(color, {
345
+ return (h(Host, { key: 'c28e5e428111f18eaceb030dca8aba5579867eaa', class: createColorClasses(color, {
345
346
  [mode]: true,
346
347
  [`${selectedButton}-active`]: datetimeActive,
347
348
  ['datetime-button-disabled']: disabled,
348
- }) }, dateText && (h("button", { key: '08ecb62da0fcbf7466a1f2403276712a3ff17fbc', class: "ion-activatable", id: "date-button", "aria-expanded": datetimeActive ? 'true' : 'false', onClick: this.handleDateClick, disabled: disabled, part: "native", ref: (el) => (this.dateTargetEl = el) }, h("slot", { key: '1c04853d4d23c0f1a594602bde44511c98355644', name: "date-target" }, dateText), mode === 'md' && h("ion-ripple-effect", { key: '5fc566cd4bc885bcf983ce99e3dc65d7f485bf9b' }))), timeText && (h("button", { key: 'c9c5c34ac338badf8659da22bea5829d62c51169', class: "ion-activatable", id: "time-button", "aria-expanded": datetimeActive ? 'true' : 'false', onClick: this.handleTimeClick, disabled: disabled, part: "native", ref: (el) => (this.timeTargetEl = el) }, h("slot", { key: '147a9d2069dbf737f6fc64787823d6d5af5aa653', name: "time-target" }, timeText), mode === 'md' && h("ion-ripple-effect", { key: '70a5e25b75ed90ac6bba003468435f67aa9d8f0a' })))));
349
+ }) }, dateText && (h("button", { key: 'a08f41b3150ef7171d0f1d9b3d69b51be67b7b84', class: "ion-activatable", id: "date-button", "aria-expanded": datetimeActive ? 'true' : 'false', onClick: this.handleDateClick, disabled: disabled, part: "native", ref: (el) => (this.dateTargetEl = el) }, h("slot", { key: '35ee68ca1cea59f03e1b59b72606e618141c8e1f', name: "date-target" }, dateText), mode === 'md' && h("ion-ripple-effect", { key: '4be53227aaf0acd20e8ebbff9f645140e8a96f33' }))), timeText && (h("button", { key: 'ab500f9b78f8c194180073eaf7325041adbd02f7', class: "ion-activatable", id: "time-button", "aria-expanded": datetimeActive ? 'true' : 'false', onClick: this.handleTimeClick, disabled: disabled, part: "native", ref: (el) => (this.timeTargetEl = el) }, h("slot", { key: '5822486dfdedba6c071a0f5233cc06f8321a1f5d', name: "time-target" }, timeText), mode === 'md' && h("ion-ripple-effect", { key: '054a4acb811a98cc6d9bb6a3cf51232a1f09b0ea' })))));
349
350
  }
350
351
  static get is() { return "ion-datetime-button"; }
351
352
  static get encapsulation() { return "shadow"; }
@@ -2,7 +2,8 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Host, h } from "@stencil/core";
5
- import { inheritAriaAttributes } from "../../utils/helpers";
5
+ import { inheritAriaAttributes, hasShadowDom } from "../../utils/helpers";
6
+ import { printIonWarning } from "../../utils/logging/index";
6
7
  import { createColorClasses, hostContext, openURL } from "../../utils/theme";
7
8
  import { close } from "ionicons/icons";
8
9
  import { getIonMode } from "../../global/ionic-global";
@@ -15,6 +16,8 @@ import { getIonMode } from "../../global/ionic-global";
15
16
  export class FabButton {
16
17
  constructor() {
17
18
  this.fab = null;
19
+ this.formButtonEl = null;
20
+ this.formEl = null;
18
21
  this.inheritedAttributes = {};
19
22
  /**
20
23
  * If `true`, the fab button will be show a close icon.
@@ -55,34 +58,105 @@ export class FabButton {
55
58
  this.onBlur = () => {
56
59
  this.ionBlur.emit();
57
60
  };
58
- this.onClick = () => {
59
- const { fab } = this;
60
- if (!fab) {
61
- return;
61
+ this.onClick = (ev) => {
62
+ const { el, fab } = this;
63
+ if (this.type !== 'button' && hasShadowDom(el)) {
64
+ this.submitForm(ev);
65
+ }
66
+ if (fab) {
67
+ fab.toggle();
62
68
  }
63
- fab.toggle();
64
69
  };
65
70
  }
71
+ disabledChanged() {
72
+ const { disabled } = this;
73
+ if (this.formButtonEl) {
74
+ this.formButtonEl.disabled = disabled;
75
+ }
76
+ }
66
77
  connectedCallback() {
67
78
  this.fab = this.el.closest('ion-fab');
68
79
  }
80
+ /**
81
+ * The native button rendered inside `ion-fab-button` is in the Shadow DOM and
82
+ * does not participate in form submission, so a hidden native button is added
83
+ * to the associated form and clicked instead.
84
+ */
85
+ renderHiddenButton() {
86
+ const formEl = (this.formEl = this.findForm());
87
+ if (formEl) {
88
+ const { formButtonEl } = this;
89
+ // Reuse the existing hidden button, syncing values that changed since it was created.
90
+ if (formButtonEl !== null && formEl.contains(formButtonEl)) {
91
+ formButtonEl.disabled = this.disabled;
92
+ formButtonEl.type = this.type;
93
+ return;
94
+ }
95
+ const newFormButtonEl = (this.formButtonEl = document.createElement('button'));
96
+ newFormButtonEl.type = this.type;
97
+ newFormButtonEl.style.display = 'none';
98
+ newFormButtonEl.disabled = this.disabled;
99
+ formEl.appendChild(newFormButtonEl);
100
+ }
101
+ }
102
+ // TODO(FW-7663): share the hidden button and form lookup with ion-button
103
+ findForm() {
104
+ const { form } = this;
105
+ if (form instanceof HTMLFormElement) {
106
+ return form;
107
+ }
108
+ if (typeof form === 'string') {
109
+ const el = document.getElementById(form);
110
+ if (el) {
111
+ if (el instanceof HTMLFormElement) {
112
+ return el;
113
+ }
114
+ else {
115
+ printIonWarning(`[ion-fab-button] - Form with selector: "#${form}" could not be found. Verify that the id is attached to a <form> element.`, this.el);
116
+ return null;
117
+ }
118
+ }
119
+ else {
120
+ printIonWarning(`[ion-fab-button] - Form with selector: "#${form}" could not be found. Verify that the id is correct and the form is rendered in the DOM.`, this.el);
121
+ return null;
122
+ }
123
+ }
124
+ if (form !== undefined) {
125
+ printIonWarning(`[ion-fab-button] - The provided "form" element is invalid. Verify that the form is a HTMLFormElement and rendered in the DOM.`, this.el);
126
+ return null;
127
+ }
128
+ return this.el.closest('form');
129
+ }
130
+ submitForm(ev) {
131
+ if (this.formEl && this.formButtonEl) {
132
+ ev.preventDefault();
133
+ this.formButtonEl.click();
134
+ }
135
+ }
69
136
  componentWillLoad() {
70
137
  this.inheritedAttributes = inheritAriaAttributes(this.el);
71
138
  }
72
139
  render() {
73
- const { el, disabled, color, href, activated, show, translucent, size, inheritedAttributes } = this;
140
+ const { el, disabled, color, href, activated, show, translucent, size, inheritedAttributes, type } = this;
74
141
  const inList = hostContext('ion-fab-list', el);
75
142
  const mode = getIonMode(this);
76
143
  const TagType = href === undefined ? 'button' : 'a';
77
144
  const attrs = TagType === 'button'
78
- ? { type: this.type }
145
+ ? { type }
79
146
  : {
80
147
  download: this.download,
81
148
  href,
82
149
  rel: this.rel,
83
150
  target: this.target,
84
151
  };
85
- return (h(Host, { key: '4eee204d20b0e2ffed49a88f6cb3e04b6697965c', onClick: this.onClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses(color, {
152
+ /**
153
+ * The hidden button is created during render so that setting `type` or
154
+ * `form` after the component loads still adds it to the form.
155
+ */
156
+ if (type !== 'button') {
157
+ this.renderHiddenButton();
158
+ }
159
+ return (h(Host, { key: '89e8f2564f12f8c1cd8509a40359202347948846', onClick: this.onClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses(color, {
86
160
  [mode]: true,
87
161
  'fab-button-in-list': inList,
88
162
  'fab-button-translucent-in-list': inList && translucent,
@@ -93,7 +167,7 @@ export class FabButton {
93
167
  'ion-activatable': true,
94
168
  'ion-focusable': true,
95
169
  [`fab-button-${size}`]: size !== undefined,
96
- }) }, h(TagType, Object.assign({ key: '914561622c0c6bd41453e828a7d8a39f924875ac' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur, onClick: (ev) => openURL(href, ev, this.routerDirection, this.routerAnimation) }, inheritedAttributes), h("ion-icon", { key: '2c8090742a64c62a79243667027a195cca9d5912', "aria-hidden": "true", icon: this.closeIcon, part: "close-icon", class: "close-icon", lazy: false }), h("span", { key: 'c3e55291e4c4d306d34a4b95dd2e727e87bdf39c', class: "button-inner" }, h("slot", { key: 'f8e57f71d8f8878d9746cfece82f57f19ef9e988' })), mode === 'md' && h("ion-ripple-effect", { key: 'a5e94fa0bb9836072300617245ed0c1b4887bac6' }))));
170
+ }) }, h(TagType, Object.assign({ key: '9e2fd462316e1aec50f53fa1b7ad7f02fd399592' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur, onClick: (ev) => type === 'button' && openURL(href, ev, this.routerDirection, this.routerAnimation) }, inheritedAttributes), h("ion-icon", { key: 'c3bdb8614eb249297afc963810066658dc85efd6', "aria-hidden": "true", icon: this.closeIcon, part: "close-icon", class: "close-icon", lazy: false }), h("span", { key: 'a6a505b24d21cc716d2e3351ad4c380b95c768d7', class: "button-inner" }, h("slot", { key: 'f44fc1e95533438ee3adc8cc5bdd9e1e85849fd6' })), mode === 'md' && h("ion-ripple-effect", { key: 'b7c7dc7fceb0bb5613239608225f040010b7b2bd' }))));
97
171
  }
98
172
  static get is() { return "ion-fab-button"; }
99
173
  static get encapsulation() { return "shadow"; }
@@ -364,6 +438,30 @@ export class FabButton {
364
438
  "attribute": "type",
365
439
  "defaultValue": "'button'"
366
440
  },
441
+ "form": {
442
+ "type": "string",
443
+ "mutable": false,
444
+ "complexType": {
445
+ "original": "string | HTMLFormElement",
446
+ "resolved": "HTMLFormElement | string | undefined",
447
+ "references": {
448
+ "HTMLFormElement": {
449
+ "location": "global",
450
+ "id": "global::HTMLFormElement"
451
+ }
452
+ }
453
+ },
454
+ "required": false,
455
+ "optional": true,
456
+ "docs": {
457
+ "tags": [],
458
+ "text": "The HTML form element or form element id. Used to submit a form when the button is not a child of the form."
459
+ },
460
+ "getter": false,
461
+ "setter": false,
462
+ "reflect": false,
463
+ "attribute": "form"
464
+ },
367
465
  "size": {
368
466
  "type": "string",
369
467
  "mutable": false,
@@ -439,4 +537,10 @@ export class FabButton {
439
537
  }];
440
538
  }
441
539
  static get elementRef() { return "el"; }
540
+ static get watchers() {
541
+ return [{
542
+ "propName": "disabled",
543
+ "methodName": "disabledChanged"
544
+ }];
545
+ }
442
546
  }
@@ -387,6 +387,17 @@ a {
387
387
  flex-direction: column;
388
388
  }
389
389
 
390
+ :host(.item-focus-indicator-room) .input-wrapper {
391
+ -webkit-margin-start: -12px;
392
+ margin-inline-start: -12px;
393
+ -webkit-margin-end: -12px;
394
+ margin-inline-end: -12px;
395
+ -webkit-padding-start: 12px;
396
+ padding-inline-start: 12px;
397
+ -webkit-padding-end: 12px;
398
+ padding-inline-end: 12px;
399
+ }
400
+
390
401
  :host(.item-multiple-inputs) ::slotted(ion-checkbox),
391
402
  :host(.item-multiple-inputs) ::slotted(ion-datetime),
392
403
  :host(.item-multiple-inputs) ::slotted(ion-radio) {
@@ -1,11 +1,12 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { Host, forceUpdate, h } from "@stencil/core";
4
+ import { Build, Host, forceUpdate, h } from "@stencil/core";
5
5
  import { inheritAttributes, raf } from "../../utils/helpers";
6
6
  import { createColorClasses, hostContext, openURL } from "../../utils/theme";
7
7
  import { chevronForward } from "ionicons/icons";
8
8
  import { getIonMode } from "../../global/ionic-global";
9
+ const INDICATOR_CONTROL_SELECTOR = 'ion-checkbox, ion-radio, ion-toggle';
9
10
  /**
10
11
  * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
11
12
  *
@@ -23,9 +24,11 @@ export class Item {
23
24
  this.labelColorStyles = {};
24
25
  this.itemStyles = new Map();
25
26
  this.inheritedAriaAttributes = {};
27
+ this.didLoad = false;
26
28
  this.multipleInputs = false;
27
29
  this.focusable = true;
28
30
  this.isInteractive = false;
31
+ this.hasSlottedIndicatorControl = false;
29
32
  /**
30
33
  * If `true`, a button tag will be rendered and the item will be tappable.
31
34
  */
@@ -51,6 +54,7 @@ export class Item {
51
54
  this.updateInteractivityOnSlotChange = () => {
52
55
  this.setIsInteractive();
53
56
  this.setMultipleInputs();
57
+ this.setHasSlottedIndicatorControl();
54
58
  };
55
59
  }
56
60
  buttonChanged() {
@@ -92,6 +96,14 @@ export class Item {
92
96
  }
93
97
  connectedCallback() {
94
98
  this.hasStartEl();
99
+ /**
100
+ * `componentDidLoad` doesn't run again when the item is moved, so re-arm the
101
+ * observer and re-read the light DOM, which may have changed while detached.
102
+ */
103
+ if (this.didLoad) {
104
+ this.watchForIndicatorControls();
105
+ this.updateInteractivityOnSlotChange();
106
+ }
95
107
  }
96
108
  componentWillLoad() {
97
109
  this.inheritedAriaAttributes = inheritAttributes(this.el, ['aria-label']);
@@ -100,8 +112,17 @@ export class Item {
100
112
  raf(() => {
101
113
  this.setMultipleInputs();
102
114
  this.setIsInteractive();
115
+ this.setHasSlottedIndicatorControl();
103
116
  this.focusable = this.isFocusable();
104
117
  });
118
+ this.watchForIndicatorControls();
119
+ this.didLoad = true;
120
+ }
121
+ disconnectedCallback() {
122
+ if (this.indicatorControlObserver) {
123
+ this.indicatorControlObserver.disconnect();
124
+ this.indicatorControlObserver = undefined;
125
+ }
105
126
  }
106
127
  totalNestedInputs() {
107
128
  // The following elements have a clickable cover that is relative to the entire item
@@ -135,6 +156,50 @@ export class Item {
135
156
  const { covers, inputs, clickables } = this.totalNestedInputs();
136
157
  this.isInteractive = covers.length > 0 || inputs.length > 0 || clickables.length > 0;
137
158
  }
159
+ /**
160
+ * `slotchange` only fires for nodes assigned directly to a slot, so a control
161
+ * inside a slotted wrapper (`<div><ion-toggle>`) never reaches
162
+ * `updateInteractivityOnSlotChange`. The light DOM is observed instead.
163
+ *
164
+ * The callback runs the whole handler because a control below a wrapper can also
165
+ * make the item multi-input, which is what decides whether the controls draw
166
+ * their own indicator at all.
167
+ *
168
+ * `:host(:has())` would avoid the observer, but the `:has()` fallback in
169
+ * `core.scss` is still open as FW-6106 and it's unreliable for slotted content
170
+ * in Android WebView. Worth revisiting when FW-6106 closes.
171
+ */
172
+ watchForIndicatorControls() {
173
+ var _a;
174
+ if (!Build.isBrowser || typeof MutationObserver === 'undefined') {
175
+ return;
176
+ }
177
+ // `Node.moveBefore` relocates the item without either callback firing, so
178
+ // never leave a previous observer behind
179
+ (_a = this.indicatorControlObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
180
+ this.indicatorControlObserver = new MutationObserver((records) => {
181
+ // The subtree observer also fires for text and hidden input churn, so only
182
+ // re-read the DOM when a control was added or removed
183
+ if (records.some(touchesIndicatorControl)) {
184
+ this.updateInteractivityOnSlotChange();
185
+ }
186
+ });
187
+ this.indicatorControlObserver.observe(this.el, { childList: true, subtree: true });
188
+ }
189
+ // These controls paint a focus indicator that overhangs their own bounds, and
190
+ // only the default slot is clipped, so only a control there needs extra room.
191
+ setHasSlottedIndicatorControl() {
192
+ const controls = this.el.querySelectorAll(INDICATOR_CONTROL_SELECTOR);
193
+ this.hasSlottedIndicatorControl = Array.from(controls).some((control) => {
194
+ // The control isn't always a direct child, so walk up to the element the item
195
+ // slots, which is the one carrying the slot name.
196
+ let slotted = control;
197
+ while (slotted !== null && slotted.parentElement !== this.el) {
198
+ slotted = slotted.parentElement;
199
+ }
200
+ return slotted !== null && !slotted.getAttribute('slot');
201
+ });
202
+ }
138
203
  // If the item contains an input including a checkbox, datetime, select, or radio
139
204
  // then the item will have a clickable input cover that covers the item
140
205
  // that should get the hover, focused and activated states UNLESS it has multiple
@@ -236,7 +301,13 @@ export class Item {
236
301
  * However, other form controls such as checkboxes and radios do.
237
302
  */
238
303
  const firstInteractiveNeedsPointerCursor = firstInteractive !== undefined && !['ION-INPUT', 'ION-TEXTAREA'].includes(firstInteractive.tagName);
239
- return (h(Host, { key: '3977491399280a2aa3f796f0549235f3dc8b5681', "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), createColorClasses(this.color, {
304
+ /**
305
+ * A control in a single-input item defers its indicator to the item, so there's
306
+ * nothing to clip and nothing to make room for. It draws its own indicator in a
307
+ * multi-input item, and in a clickable item, which is a second tab stop.
308
+ */
309
+ const slottedIndicatorNeedsRoom = this.hasSlottedIndicatorControl && (multipleInputs || this.isClickable());
310
+ return (h(Host, { key: '0dc161ad7ac2fdedc5005015fceb34ed176010a6', "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), createColorClasses(this.color, {
240
311
  item: true,
241
312
  [mode]: true,
242
313
  'item-lines-default': lines === undefined,
@@ -245,10 +316,11 @@ export class Item {
245
316
  'item-disabled': disabled,
246
317
  'in-list': inList,
247
318
  'item-multiple-inputs': this.multipleInputs,
319
+ 'item-focus-indicator-room': slottedIndicatorNeedsRoom,
248
320
  'ion-activatable': canActivate,
249
321
  'ion-focusable': this.focusable,
250
322
  'item-rtl': document.dir === 'rtl',
251
- })), role: inList ? 'listitem' : null }, h(TagType, Object.assign({ key: '8aff72457004553e83d74a4dda2f9a6b8cf523d7' }, attrs, inheritedAriaAttributes, { class: "item-native", part: "native", disabled: disabled }, clickFn), h("slot", { key: 'aba9a0f2ad101be6598f364c83236ca3de437a04', name: "start", onSlotchange: this.updateInteractivityOnSlotChange }), h("div", { key: '5e7851e49959e4ed114fcd51cb167db9bd8c0f00', class: "item-inner", part: "inner" }, h("div", { key: 'eee6511845d8459450ca856819839d83cd507c0c', class: "input-wrapper", part: "container" }, h("slot", { key: '76e13f86cd144951b1999eb333a2027b42cdf6e2', onSlotchange: this.updateInteractivityOnSlotChange })), h("slot", { key: 'b264f7b4c367e7700a9a05343177abf7b17045d8', name: "end", onSlotchange: this.updateInteractivityOnSlotChange }), showDetail && (h("ion-icon", { key: '66b930bdfebb55ea0b2e784a1c902eb6c64b2370', icon: detailIcon, lazy: false, class: "item-detail-icon", part: "detail-icon", "aria-hidden": "true", "flip-rtl": detailIcon === chevronForward }))), canActivate && mode === 'md' && h("ion-ripple-effect", { key: '06ab8e87917b545f3c221602b9b6d8004a1895ad' }))));
323
+ })), role: inList ? 'listitem' : null }, h(TagType, Object.assign({ key: 'd42583cf62133b2553de2e7a5357168640bb1834' }, attrs, inheritedAriaAttributes, { class: "item-native", part: "native", disabled: disabled }, clickFn), h("slot", { key: 'bd805ce49946cd52280d1c4b370104ee2472f1e2', name: "start", onSlotchange: this.updateInteractivityOnSlotChange }), h("div", { key: '0de589f9c5c13c31b6c8838566a7ad6f78f81028', class: "item-inner", part: "inner" }, h("div", { key: 'faaf6a39e072fb352753c645736ab1f47c2d7d25', class: "input-wrapper", part: "container" }, h("slot", { key: '70a34c31de29a0786a14b4a7d3156ba8d4d92eb4', onSlotchange: this.updateInteractivityOnSlotChange })), h("slot", { key: 'c8e764b62c5c30f67750896741ca266b741d5999', name: "end", onSlotchange: this.updateInteractivityOnSlotChange }), showDetail && (h("ion-icon", { key: 'af006856d8c4ed69174534261344215957cc751a', icon: detailIcon, lazy: false, class: "item-detail-icon", part: "detail-icon", "aria-hidden": "true", "flip-rtl": detailIcon === chevronForward }))), canActivate && mode === 'md' && h("ion-ripple-effect", { key: 'e5e151bafedd38feb8f71a387a0059aa1e9e90de' }))));
252
324
  }
253
325
  static get is() { return "ion-item"; }
254
326
  static get encapsulation() { return "shadow"; }
@@ -543,7 +615,8 @@ export class Item {
543
615
  return {
544
616
  "multipleInputs": {},
545
617
  "focusable": {},
546
- "isInteractive": {}
618
+ "isInteractive": {},
619
+ "hasSlottedIndicatorControl": {}
547
620
  };
548
621
  }
549
622
  static get elementRef() { return "el"; }
@@ -569,3 +642,11 @@ export class Item {
569
642
  }];
570
643
  }
571
644
  }
645
+ const isIndicatorControl = (node) => {
646
+ if (node.nodeType !== Node.ELEMENT_NODE) {
647
+ return false;
648
+ }
649
+ const el = node;
650
+ return el.matches(INDICATOR_CONTROL_SELECTOR) || el.querySelector(INDICATOR_CONTROL_SELECTOR) !== null;
651
+ };
652
+ const touchesIndicatorControl = (record) => Array.from(record.addedNodes).some(isIndicatorControl) || Array.from(record.removedNodes).some(isIndicatorControl);