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
@@ -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) {
@@ -224,6 +224,13 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
224
224
  return true;
225
225
  };
226
226
  const onStart = (detail) => {
227
+ var _a;
228
+ /**
229
+ * Firefox automatically selects the header text during drag
230
+ * due to the focusable wrapper (tabindex="-1"). Remove any
231
+ * selection that may have occurred.
232
+ */
233
+ (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
227
234
  /**
228
235
  * If canDismiss is anything other than `true`
229
236
  * then users should be able to swipe down
@@ -96,7 +96,14 @@ export const createSwipeToCloseGesture = (el, animation, statusBarStyle, onDismi
96
96
  return false;
97
97
  };
98
98
  const onStart = (detail) => {
99
+ var _a;
99
100
  const { deltaY } = detail;
101
+ /**
102
+ * Firefox automatically selects the header text during drag
103
+ * due to the focusable wrapper (tabindex="-1"). Remove any
104
+ * selection that may have occurred.
105
+ */
106
+ (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
100
107
  /**
101
108
  * Get the initial scrollY value so
102
109
  * that we can correctly reset the scrollY
@@ -126,6 +126,14 @@ ion-backdrop {
126
126
  z-index: 10;
127
127
  }
128
128
 
129
+ /**
130
+ * The wrapper receives programmatic focus for screen readers but should not
131
+ * show a visible focus ring, which is meant only for keyboard navigation.
132
+ */
133
+ .modal-wrapper {
134
+ outline: none;
135
+ }
136
+
129
137
  .modal-shadow {
130
138
  position: absolute;
131
139
  background: transparent;
@@ -191,9 +191,15 @@ export class Modal {
191
191
  * if it exists and is focusable, otherwise let the focus trap handle it.
192
192
  */
193
193
  this.onModalFocus = (ev) => {
194
+ var _a;
194
195
  const { dragHandleEl, el } = this;
195
- // Only handle focus if the modal itself was focused (not a child element)
196
- if (ev.target === el && dragHandleEl && dragHandleEl.tabIndex !== -1) {
196
+ /**
197
+ * Shadow DOM focus is retargeted to the host, so `ev.target === el` is also
198
+ * true when a shadow child (the dialog wrapper present() focuses) is focused.
199
+ * shadowRoot's activeElement is null only when the host was focused directly,
200
+ * so redirect to the handle only then and leave present()'s wrapper focus intact.
201
+ */
202
+ if (ev.target === el && ((_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement) == null && dragHandleEl && dragHandleEl.tabIndex !== -1) {
197
203
  dragHandleEl.focus();
198
204
  }
199
205
  };
@@ -1162,20 +1168,26 @@ export class Modal {
1162
1168
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1163
1169
  const isHandleCycle = handleBehavior === 'cycle';
1164
1170
  const isSheetModalWithHandle = isSheetModal && showHandle;
1165
- return (h(Host, Object.assign({ key: 'ab93657f4a5b4c97168d292b398d975e4b5b4af4', "no-router": true,
1171
+ return (h(Host, Object.assign({ key: 'bad33199a939bd1e289b3b3f936c676ba403224f', "no-router": true,
1166
1172
  // Allow the modal to be navigable when the handle is focusable
1167
1173
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1168
1174
  zIndex: `${20000 + this.overlayIndex}`,
1169
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '5ba6315c7c8c64fa04c62f85fd493da5275b2cbd', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'a76c48d41c67f86c18a7481e980e6925e614f85d', class: "modal-shadow" }), h("div", Object.assign({ key: '3f6c61ba60b063592784d76a9594073b3bf210a9',
1175
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: 'd098688a65fb2d1befb255f0e59fd4c00b401092', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '96cd2e59a7d1c0de7c9afb8f8d366367b09fbc93', class: "modal-shadow" }), h("div", Object.assign({ key: '882ccc39ce344a2c51d6508c11c3f40b26eaf2a7',
1170
1176
  /*
1171
1177
  role and aria-modal must be used on the
1172
1178
  same element. They must also be set inside the
1173
1179
  shadow DOM otherwise ion-button will not be highlighted
1174
1180
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1181
+
1182
+ tabIndex={-1} is required so present() can move focus to this
1183
+ element (which carries the dialog role) instead of the role-less
1184
+ host. role="dialog" alone does not make an element focusable, so
1185
+ without the tabindex focus() would be a no-op and screen readers
1186
+ may not properly announce the dialog and its content when it opens.
1175
1187
  */
1176
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '7f66b4fdb3dec61a2b9aec4e9dbacf5d013d637c', class: "modal-handle",
1188
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", tabIndex: -1, class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '714d9904fcc8105b21e2bb66cdc032bffd685d72', class: "modal-handle",
1177
1189
  // Prevents the handle from receiving keyboard focus when it does not cycle
1178
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '2dfaaebe3d0b2a46be1224c25f9ab89bf1744332', onSlotchange: this.onSlotChange }))));
1190
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '79dfbeb3fe5eee7dc3a243ad880d3f63184f3ab7', onSlotchange: this.onSlotChange }))));
1179
1191
  }
1180
1192
  static get is() { return "ion-modal"; }
1181
1193
  static get encapsulation() { return "shadow"; }
@@ -126,6 +126,14 @@ ion-backdrop {
126
126
  z-index: 10;
127
127
  }
128
128
 
129
+ /**
130
+ * The wrapper receives programmatic focus for screen readers but should not
131
+ * show a visible focus ring, which is meant only for keyboard navigation.
132
+ */
133
+ .modal-wrapper {
134
+ outline: none;
135
+ }
136
+
129
137
  .modal-shadow {
130
138
  position: absolute;
131
139
  background: transparent;
@@ -130,6 +130,17 @@ input {
130
130
  width: auto;
131
131
  }
132
132
 
133
+ :host(.ion-focused) .radio-icon::after {
134
+ border-radius: var(--inner-border-radius);
135
+ display: block;
136
+ position: absolute;
137
+ width: 36px;
138
+ height: 36px;
139
+ background: var(--ion-color-primary-tint, #1a65eb);
140
+ content: "";
141
+ opacity: 0.2;
142
+ }
143
+
133
144
  .radio-wrapper {
134
145
  display: flex;
135
146
  position: relative;
@@ -456,21 +467,6 @@ input {
456
467
  opacity: 0.3;
457
468
  }
458
469
 
459
- :host(.ion-focused) .radio-icon::after {
460
- border-radius: var(--inner-border-radius);
461
- top: -8px;
462
- display: block;
463
- position: absolute;
464
- width: 36px;
465
- height: 36px;
466
- background: var(--ion-color-primary-tint, #1a65eb);
467
- content: "";
468
- opacity: 0.2;
469
- }
470
- :host(.ion-focused) .radio-icon::after {
471
- inset-inline-start: -9px;
472
- }
473
-
474
470
  .native-wrapper .radio-icon {
475
471
  width: 0.9375rem;
476
472
  height: 1.5rem;
@@ -1,8 +1,8 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { Host, h } from "@stencil/core";
5
- import { isOptionSelected } from "../../utils/forms/index";
4
+ import { Host, forceUpdate, h } from "@stencil/core";
5
+ import { createItemMultipleInputsObserver, isOptionSelected } from "../../utils/forms/index";
6
6
  import { addEventListener, removeEventListener } from "../../utils/helpers";
7
7
  import { createColorClasses, hostContext } from "../../utils/theme";
8
8
  import { getIonMode } from "../../global/ionic-global";
@@ -118,6 +118,7 @@ export class Radio {
118
118
  this.updateState();
119
119
  addEventListener(radioGroup, 'ionValueChange', this.updateState);
120
120
  }
121
+ this.itemFocusObserver = createItemMultipleInputsObserver(this.el, () => forceUpdate(this));
121
122
  }
122
123
  disconnectedCallback() {
123
124
  const radioGroup = this.radioGroup;
@@ -125,6 +126,10 @@ export class Radio {
125
126
  removeEventListener(radioGroup, 'ionValueChange', this.updateState);
126
127
  this.radioGroup = null;
127
128
  }
129
+ if (this.itemFocusObserver) {
130
+ this.itemFocusObserver.disconnect();
131
+ this.itemFocusObserver = undefined;
132
+ }
128
133
  }
129
134
  get hasLabel() {
130
135
  return this.el.textContent !== '';
@@ -136,7 +141,8 @@ export class Radio {
136
141
  const { checked, disabled, color, el, justify, labelPlacement, hasLabel, buttonTabindex, alignment } = this;
137
142
  const mode = getIonMode(this);
138
143
  const inItem = hostContext('ion-item', el);
139
- return (h(Host, { key: 'af5dc59ed528150872e907ed1036e3e2decba939', onFocus: this.onFocus, onBlur: this.onBlur, onClick: this.onClick, class: createColorClasses(color, {
144
+ const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el);
145
+ return (h(Host, { key: '8622be22eb4f0bab791b479c813c490e21fd9ad8', onFocus: this.onFocus, onBlur: this.onBlur, onClick: this.onClick, class: createColorClasses(color, {
140
146
  [mode]: true,
141
147
  'in-item': inItem,
142
148
  'radio-checked': checked,
@@ -144,13 +150,15 @@ export class Radio {
144
150
  [`radio-justify-${justify}`]: justify !== undefined,
145
151
  [`radio-alignment-${alignment}`]: alignment !== undefined,
146
152
  [`radio-label-placement-${labelPlacement}`]: true,
147
- // Focus and active styling should not apply when the radio is in an item
153
+ // A single-input item has the input cover and draws the indicator itself.
154
+ // A multi-input item has no cover, so each control draws its own. Active
155
+ // styling stays suppressed either way.
148
156
  'ion-activatable': !inItem,
149
- 'ion-focusable': !inItem,
150
- }), role: "radio", "aria-checked": checked ? 'true' : 'false', "aria-disabled": disabled ? 'true' : null, tabindex: buttonTabindex }, h("label", { key: '1312aba3259a87fe23a6911cdfa477e309469c8b', class: "radio-wrapper" }, h("div", { key: 'b342aa27e240a300836d2a135658d346b95daf09', class: {
157
+ 'ion-focusable': !inItem || inMultipleInputsItem,
158
+ }), role: "radio", "aria-checked": checked ? 'true' : 'false', "aria-disabled": disabled ? 'true' : null, tabindex: buttonTabindex }, h("label", { key: 'fb358e14faf37142ccad169adb729f3129371406', class: "radio-wrapper" }, h("div", { key: '844ab615aadf6eef46368ad060afaca9c4222cc1', class: {
151
159
  'label-text-wrapper': true,
152
160
  'label-text-wrapper-hidden': !hasLabel,
153
- }, part: "label" }, h("slot", { key: '0a4613d29aa783d1882cf889377f7e4fd4fea51d' })), h("div", { key: '191faea79dc4cd8befc4b873f5bd9f5af8ca2acc', class: "native-wrapper" }, this.renderRadioControl()))));
161
+ }, part: "label" }, h("slot", { key: 'a57e53d65afda0f2082071c911343a0eb02a00a6' })), h("div", { key: '206ff11e9c2c1946a389f96a3e642335e9c3add6', class: "native-wrapper" }, this.renderRadioControl()))));
154
162
  }
155
163
  static get is() { return "ion-radio"; }
156
164
  static get encapsulation() { return "shadow"; }
@@ -130,6 +130,17 @@ input {
130
130
  width: auto;
131
131
  }
132
132
 
133
+ :host(.ion-focused) .radio-icon::after {
134
+ border-radius: var(--inner-border-radius);
135
+ display: block;
136
+ position: absolute;
137
+ width: 36px;
138
+ height: 36px;
139
+ background: var(--ion-color-primary-tint, #1a65eb);
140
+ content: "";
141
+ opacity: 0.2;
142
+ }
143
+
133
144
  .radio-wrapper {
134
145
  display: flex;
135
146
  position: relative;
@@ -477,17 +488,6 @@ input {
477
488
  opacity: 0.63;
478
489
  }
479
490
 
480
- :host(.ion-focused) .radio-icon::after {
481
- border-radius: var(--inner-border-radius);
482
- display: block;
483
- position: absolute;
484
- width: 36px;
485
- height: 36px;
486
- background: var(--ion-color-primary-tint, #1a65eb);
487
- content: "";
488
- opacity: 0.2;
489
- }
490
-
491
491
  .native-wrapper .radio-icon {
492
492
  width: 1.25rem;
493
493
  height: 1.25rem;
@@ -203,7 +203,16 @@ export class Refresher {
203
203
  });
204
204
  };
205
205
  this.scrollEl.addEventListener('scroll', this.scrollListenerCallback);
206
- this.gesture = (await import('../../utils/gesture')).createGesture({
206
+ const { createGesture } = await import('../../utils/gesture');
207
+ /**
208
+ * Awaiting the dynamic import yields to the event loop, so the component can
209
+ * disconnect before it resolves. disconnectedCallback clears scrollEl, so a
210
+ * missing scrollEl here means there is nothing left to attach a gesture to.
211
+ */
212
+ if (!this.scrollEl) {
213
+ return;
214
+ }
215
+ this.gesture = createGesture({
207
216
  el: this.scrollEl,
208
217
  gestureName: 'refresher',
209
218
  gesturePriority: 31,
@@ -263,7 +272,16 @@ export class Refresher {
263
272
  refreshingCircle.style.setProperty('animation-delay', '-655ms');
264
273
  });
265
274
  }
266
- this.gesture = (await import('../../utils/gesture')).createGesture({
275
+ const { createGesture } = await import('../../utils/gesture');
276
+ /**
277
+ * Awaiting the dynamic import yields to the event loop, so the component can
278
+ * disconnect before it resolves. disconnectedCallback clears scrollEl, so a
279
+ * missing scrollEl here means there is nothing left to attach a gesture to.
280
+ */
281
+ if (!this.scrollEl) {
282
+ return;
283
+ }
284
+ this.gesture = createGesture({
267
285
  el: this.scrollEl,
268
286
  gestureName: 'refresher',
269
287
  gesturePriority: 31,
@@ -397,7 +415,16 @@ export class Refresher {
397
415
  this.setupNativeRefresher(contentEl);
398
416
  }
399
417
  else {
400
- this.gesture = (await import('../../utils/gesture')).createGesture({
418
+ const { createGesture } = await import('../../utils/gesture');
419
+ /**
420
+ * Awaiting the dynamic import yields to the event loop, so the component can
421
+ * disconnect before it resolves. A disconnected contentEl means there is
422
+ * nothing left to attach a gesture to.
423
+ */
424
+ if (!contentEl.isConnected) {
425
+ return;
426
+ }
427
+ this.gesture = createGesture({
401
428
  el: contentEl,
402
429
  gestureName: 'refresher',
403
430
  gesturePriority: 31,
@@ -716,7 +743,7 @@ export class Refresher {
716
743
  }
717
744
  render() {
718
745
  const mode = getIonMode(this);
719
- return (h(Host, { key: '80fa313b0d77161680836bdd6aa334d02c549b41', slot: "fixed", class: {
746
+ return (h(Host, { key: '2eaca7f85b3d40418f213077434e519dfacd9d42', slot: "fixed", class: {
720
747
  [mode]: true,
721
748
  // Used internally for styling
722
749
  [`refresher-${mode}`]: true,
@@ -836,8 +836,9 @@ export class Select {
836
836
  *
837
837
  * TODO(FW-5592): Remove hasStartEndSlots condition
838
838
  */
839
- const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
840
- return (h(Host, { key: '5f4ee901b65554eacc9fa52230d06b1a5ff1247c', onClick: this.onClick, class: createColorClasses(this.color, {
839
+ const labelShouldFloat = labelPlacement === 'stacked' ||
840
+ (labelPlacement === 'floating' && (hasValue || hasFocus || isExpanded || hasStartEndSlots));
841
+ return (h(Host, { key: 'f16327d6866ffee76e1618fcd3281590ca0fd56d', onClick: this.onClick, class: createColorClasses(this.color, {
841
842
  [mode]: true,
842
843
  'in-item': inItem,
843
844
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -855,7 +856,7 @@ export class Select {
855
856
  [`select-justify-${justify}`]: justifyEnabled,
856
857
  [`select-shape-${shape}`]: shape !== undefined,
857
858
  [`select-label-placement-${labelPlacement}`]: true,
858
- }) }, h("label", { key: 'd56d89b5b58da2b5eaebada9d1be58ca45c67806', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, this.renderLabelContainer(), h("div", { key: 'bc8bf089c693781ad5b3a5db76a12223867d748e', class: "select-wrapper-inner", part: "inner" }, h("slot", { key: 'e1c12b96f1573eb192608c458ed1f3463174cd89', name: "start" }), h("div", { key: '6ee5e3900d8e462f175649b502ac6faa2f2b9909', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'faeaea34aad412e1458beab95d4b1b9ca7b87c41', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: 'aeff51e51c990844869eff9940575ce6b2223135', class: "select-highlight" })), this.renderBottomContent()));
859
+ }) }, h("label", { key: 'db13f76b384a7a7461c1ad752108eadf9c0213be', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, this.renderLabelContainer(), h("div", { key: '2fb0e57bd596c0f525be79293b7da4aca55a9385', class: "select-wrapper-inner", part: "inner" }, h("slot", { key: 'cd694c36242789a89461ed3412af9891ad68411b', name: "start" }), h("div", { key: 'fdf62911be54f4ab29eb3c2eb153f082e76a8f49', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'a32f5c4b101d4d6ad45870f4c4446960009377f5', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '61f792024a5f221ef4f28a3c249bd3a0de5edeab', class: "select-highlight" })), this.renderBottomContent()));
859
860
  }
860
861
  static get is() { return "ion-select"; }
861
862
  static get encapsulation() { return "shadow"; }
@@ -6,7 +6,6 @@ import { attachComponent } from "../../utils/framework-delegate";
6
6
  import { printIonError } from "../../utils/logging/index";
7
7
  export class Tab {
8
8
  constructor() {
9
- this.loaded = false;
10
9
  /** @internal */
11
10
  this.active = false;
12
11
  }
@@ -30,27 +29,35 @@ export class Tab {
30
29
  }
31
30
  changeActive(isActive) {
32
31
  if (isActive) {
33
- this.prepareLazyLoaded();
32
+ this.prepareLazyLoaded().catch((e) => {
33
+ printIonError('[ion-tab] - Exception in prepareLazyLoaded:', e);
34
+ });
34
35
  }
35
36
  }
36
37
  prepareLazyLoaded() {
37
- if (!this.loaded && this.component != null) {
38
- this.loaded = true;
39
- try {
40
- return attachComponent(this.delegate, this.el, this.component, ['ion-page']);
41
- }
42
- catch (e) {
43
- printIonError('[ion-tab] - Exception in prepareLazyLoaded:', e);
44
- }
38
+ if (this.component == null) {
39
+ return Promise.resolve(undefined);
40
+ }
41
+ /**
42
+ * The attach is cached so that concurrent activations share a single
43
+ * attempt and later activations reuse the result. The cache is cleared
44
+ * only when the attach fails, so a tab whose component failed to load
45
+ * can be retried the next time it is activated.
46
+ */
47
+ if (this.loadPromise === undefined) {
48
+ this.loadPromise = attachComponent(this.delegate, this.el, this.component, ['ion-page']).catch((e) => {
49
+ this.loadPromise = undefined;
50
+ throw e;
51
+ });
45
52
  }
46
- return Promise.resolve(undefined);
53
+ return this.loadPromise;
47
54
  }
48
55
  render() {
49
56
  const { tab, active, component } = this;
50
- return (h(Host, { key: 'fbd837bad7a0632336d46a597ace23673b153e48', role: "tabpanel", "aria-hidden": !active ? 'true' : null, "aria-labelledby": `tab-button-${tab}`, class: {
57
+ return (h(Host, { key: 'c35c9f96ed3a73270b23f30d9e279dcad9032e28', role: "tabpanel", "aria-hidden": !active ? 'true' : null, "aria-labelledby": `tab-button-${tab}`, class: {
51
58
  'ion-page': component === undefined,
52
59
  'tab-hidden': !active,
53
- } }, h("slot", { key: '35c218169fda826c9c1337558e0278d0c7f5f26a' })));
60
+ } }, h("slot", { key: '1c03523327e571f9ba140be0ccd6fe089e16a77a' })));
54
61
  }
55
62
  static get is() { return "ion-tab"; }
56
63
  static get encapsulation() { return "shadow"; }
@@ -98,10 +98,6 @@
98
98
  width: auto;
99
99
  }
100
100
 
101
- :host(.ion-focused) input {
102
- border: 2px solid #5e9ed6;
103
- }
104
-
105
101
  :host(.toggle-disabled) {
106
102
  pointer-events: none;
107
103
  }
@@ -114,6 +110,10 @@ input {
114
110
  display: none;
115
111
  }
116
112
 
113
+ :host(:focus) {
114
+ outline: none;
115
+ }
116
+
117
117
  .toggle-wrapper {
118
118
  display: flex;
119
119
  position: relative;
@@ -635,4 +635,12 @@ input {
635
635
 
636
636
  :host(.toggle-disabled) {
637
637
  opacity: 0.3;
638
+ }
639
+
640
+ :host(.ion-focused:not(.toggle-defers-indicator)) .toggle-icon {
641
+ box-shadow: 0 0 0 4px rgba(var(--ion-color-primary-rgb, 0, 84, 233), 0.2);
642
+ }
643
+
644
+ :host(.ion-color.ion-focused.toggle-checked:not(.toggle-defers-indicator)) .toggle-icon {
645
+ box-shadow: 0 0 0 4px rgba(var(--ion-color-base-rgb), 0.2);
638
646
  }
@@ -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 { renderHiddenInput, inheritAriaAttributes } from "../../utils/helpers";
7
7
  import { hapticSelection } from "../../utils/native/haptic";
8
8
  import { isPlatform } from "../../utils/platform";
@@ -196,6 +196,10 @@ export class Toggle {
196
196
  }
197
197
  // Always set initial state
198
198
  this.isInvalid = checkInvalidState(el);
199
+ this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this), [
200
+ 'item-multiple-inputs',
201
+ 'ion-activatable',
202
+ ]);
199
203
  }
200
204
  componentDidLoad() {
201
205
  this.setupGesture();
@@ -206,6 +210,10 @@ export class Toggle {
206
210
  this.gesture.destroy();
207
211
  this.gesture = undefined;
208
212
  }
213
+ if (this.itemFocusObserver) {
214
+ this.itemFocusObserver.disconnect();
215
+ this.itemFocusObserver = undefined;
216
+ }
209
217
  // Clean up validation observer to prevent memory leaks.
210
218
  if (this.validationObserver) {
211
219
  this.validationObserver.disconnect();
@@ -286,10 +294,20 @@ export class Toggle {
286
294
  const mode = getIonMode(this);
287
295
  const value = this.getValue();
288
296
  const rtl = isRTL(el) ? 'rtl' : 'ltr';
297
+ const inItem = hostContext('ion-item', el);
298
+ const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el);
299
+ // A clickable item is a second tab stop painting the same row indicator, which
300
+ // `item-multiple-inputs` misses because it counts cover elements, not toggles.
301
+ // The attributes are matched too because the class needs the item to render.
302
+ const inClickableItem = hostContext('ion-item.ion-activatable, ion-item[button], ion-item[href]', el);
289
303
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
290
- return (h(Host, { key: '736e1dcfca34fd41f6f0632652e7f86d6996a232', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses(color, {
304
+ return (h(Host, { key: 'e2fb9e7fb497511a8f763895fab34f20d5f8cb9e', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses(color, {
291
305
  [mode]: true,
292
- 'in-item': hostContext('ion-item', el),
306
+ 'in-item': inItem,
307
+ // `ion-focusable` has to stay on because it is what makes the item focusable.
308
+ // When the item draws the row indicator, CSS suppresses ours instead.
309
+ 'ion-focusable': true,
310
+ 'toggle-defers-indicator': inItem && !inMultipleInputsItem && !inClickableItem,
293
311
  'toggle-activated': activated,
294
312
  'toggle-checked': checked,
295
313
  'toggle-disabled': disabled,
@@ -297,10 +315,10 @@ export class Toggle {
297
315
  [`toggle-alignment-${alignment}`]: alignment !== undefined,
298
316
  [`toggle-label-placement-${labelPlacement}`]: true,
299
317
  [`toggle-${rtl}`]: true,
300
- }) }, h("label", { key: 'e697739fd3964c1c6c23331cf7e38a9841c46a6a', class: "toggle-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: '3d500b8c1bc879591249d183c1abb3f5f26aff6b', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), h("div", { key: 'e38f3fe0e66b4ca434fd81514f5ef771c2960841', class: {
318
+ }) }, h("label", { key: 'ab95b2af493f6352abefbc8661b112796ed12035', class: "toggle-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: 'f5a96b8f56787d67fa4ed2c29a089cd2fa02cb00', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), h("div", { key: '3bdabb3c25ec4417b0e968bae12f7365a77acb65', class: {
301
319
  'label-text-wrapper': true,
302
320
  'label-text-wrapper-hidden': !hasLabel,
303
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '006dd3558264d69ab020c07005b348adc4ecdd5b' }), this.renderHintText()), h("div", { key: 'dd4e52de31caabee41ec1bc58efecd1e213368c9', class: "native-wrapper" }, this.renderToggleControl()))));
321
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '3de9890950b5066173d49a6d615e415d286e4aa5' }), this.renderHintText()), h("div", { key: 'de233f83efd106d043b31cceb5eb9a6e5758d032', class: "native-wrapper" }, this.renderToggleControl()))));
304
322
  }
305
323
  static get is() { return "ion-toggle"; }
306
324
  static get encapsulation() { return "shadow"; }
@@ -98,10 +98,6 @@
98
98
  width: auto;
99
99
  }
100
100
 
101
- :host(.ion-focused) input {
102
- border: 2px solid #5e9ed6;
103
- }
104
-
105
101
  :host(.toggle-disabled) {
106
102
  pointer-events: none;
107
103
  }
@@ -114,6 +110,10 @@ input {
114
110
  display: none;
115
111
  }
116
112
 
113
+ :host(:focus) {
114
+ outline: none;
115
+ }
116
+
117
117
  .toggle-wrapper {
118
118
  display: flex;
119
119
  position: relative;
@@ -528,7 +528,7 @@ input {
528
528
  --handle-width: 20px;
529
529
  --handle-height: 20px;
530
530
  --handle-max-height: calc(100% + 6px);
531
- --handle-spacing: 0;
531
+ --handle-spacing: 0px;
532
532
  --handle-transition: transform 160ms cubic-bezier(0.4, 0, 0.2, 1), background-color 160ms cubic-bezier(0.4, 0, 0.2, 1);
533
533
  }
534
534
 
@@ -572,6 +572,43 @@ input {
572
572
  height: 100%;
573
573
  }
574
574
 
575
+ :host(.ion-focused:not(.toggle-defers-indicator)) .toggle-icon-wrapper::after {
576
+ border-radius: 50%;
577
+ display: block;
578
+ position: absolute;
579
+ width: 36px;
580
+ height: 36px;
581
+ transform: translateY(-50%);
582
+ background: rgba(var(--ion-text-color-rgb, 0, 0, 0), 0.12);
583
+ content: "";
584
+ z-index: -1;
585
+ inset-block-start: 50%;
586
+ }
587
+
588
+ :host(.toggle-ltr.ion-focused) .toggle-icon-wrapper::after {
589
+ left: calc(var(--handle-spacing) + (var(--handle-width) - 36px) / 2);
590
+ }
591
+
592
+ :host(.toggle-rtl.ion-focused) .toggle-icon-wrapper::after {
593
+ right: calc(var(--handle-spacing) + (var(--handle-width) - 36px) / 2);
594
+ }
595
+
596
+ :host(.toggle-ltr.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
597
+ transform: translate(calc(var(--handle-spacing) * -2), -50%);
598
+ }
599
+
600
+ :host(.toggle-rtl.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
601
+ transform: translate(calc(var(--handle-spacing) * 2), -50%);
602
+ }
603
+
604
+ :host(.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
605
+ background: rgba(var(--ion-color-primary-rgb, 0, 84, 233), 0.2);
606
+ }
607
+
608
+ :host(.ion-color.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
609
+ background: rgba(var(--ion-color-base-rgb), 0.2);
610
+ }
611
+
575
612
  :host(.toggle-disabled) {
576
613
  opacity: 0.38;
577
614
  }
@@ -3,4 +3,5 @@
3
3
  */
4
4
  export * from './notch-controller';
5
5
  export * from './compare-with-utils';
6
+ export * from './item-multiple-inputs';
6
7
  export * from './validity';
@@ -0,0 +1,33 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ import { Build } from "@stencil/core";
5
+ /**
6
+ * `item-multiple-inputs` is toggled on the item after its form controls render,
7
+ * so a control whose focus styling depends on it needs to re-render when it flips.
8
+ *
9
+ * @internal
10
+ * @param el The form control whose closest `ion-item` should be observed.
11
+ * @param onChange Called whenever one of `classNames` is added or removed.
12
+ * @param classNames The item classes the caller's rendering depends on. A caller
13
+ * reading more than one must list them all, or changes to the rest are ignored.
14
+ * @returns The observer to disconnect in `disconnectedCallback`, or `undefined`
15
+ * if it could not be created.
16
+ */
17
+ export const createItemMultipleInputsObserver = (el, onChange, classNames = ['item-multiple-inputs']) => {
18
+ const item = el.closest('ion-item');
19
+ if (!item || !Build.isBrowser || typeof MutationObserver === 'undefined') {
20
+ return undefined;
21
+ }
22
+ const readClasses = () => classNames.map((name) => item.classList.contains(name)).join(',');
23
+ let previousClasses = readClasses();
24
+ const observer = new MutationObserver(() => {
25
+ const currentClasses = readClasses();
26
+ if (currentClasses !== previousClasses) {
27
+ previousClasses = currentClasses;
28
+ onChange();
29
+ }
30
+ });
31
+ observer.observe(item, { attributes: true, attributeFilter: ['class'] });
32
+ return observer;
33
+ };