@ionic/core 8.7.3-dev.11755600455.1e79c35a → 8.7.3-dev.11755696506.17b8097b

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 (50) hide show
  1. package/components/ion-refresher.js +18 -1
  2. package/components/overlays.js +102 -2
  3. package/dist/cjs/index.cjs.js +1 -1
  4. package/dist/cjs/ion-action-sheet.cjs.entry.js +1 -1
  5. package/dist/cjs/ion-alert.cjs.entry.js +1 -1
  6. package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
  7. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  8. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  9. package/dist/cjs/ion-modal.cjs.entry.js +1 -1
  10. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  11. package/dist/cjs/ion-refresher_2.cjs.entry.js +18 -1
  12. package/dist/cjs/ion-select-modal.cjs.entry.js +1 -1
  13. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  14. package/dist/cjs/ion-toast.cjs.entry.js +1 -1
  15. package/dist/cjs/{overlays-DUsEBICv.js → overlays-DFkeeMZX.js} +101 -1
  16. package/dist/collection/components/refresher/refresher.js +18 -1
  17. package/dist/collection/utils/overlays.js +102 -1
  18. package/dist/docs.json +1 -1
  19. package/dist/esm/index.js +1 -1
  20. package/dist/esm/ion-action-sheet.entry.js +1 -1
  21. package/dist/esm/ion-alert.entry.js +1 -1
  22. package/dist/esm/ion-datetime_3.entry.js +1 -1
  23. package/dist/esm/ion-loading.entry.js +1 -1
  24. package/dist/esm/ion-menu_3.entry.js +1 -1
  25. package/dist/esm/ion-modal.entry.js +1 -1
  26. package/dist/esm/ion-popover.entry.js +1 -1
  27. package/dist/esm/ion-refresher_2.entry.js +18 -1
  28. package/dist/esm/ion-select-modal.entry.js +1 -1
  29. package/dist/esm/ion-select_3.entry.js +1 -1
  30. package/dist/esm/ion-toast.entry.js +1 -1
  31. package/dist/esm/{overlays-B_dsLNe8.js → overlays-BfCgLYdD.js} +102 -2
  32. package/dist/ionic/index.esm.js +1 -1
  33. package/dist/ionic/ionic.esm.js +1 -1
  34. package/dist/ionic/{p-698fb72c.entry.js → p-1e6a6fde.entry.js} +1 -1
  35. package/dist/ionic/{p-98d0823e.entry.js → p-31f7216f.entry.js} +1 -1
  36. package/dist/ionic/{p-57bb1214.entry.js → p-4d57f91a.entry.js} +1 -1
  37. package/dist/ionic/{p-8bfe00f3.entry.js → p-5c138549.entry.js} +1 -1
  38. package/dist/ionic/p-8cdb4ff5.entry.js +4 -0
  39. package/dist/ionic/{p-09ed68cf.entry.js → p-9f8f01e6.entry.js} +1 -1
  40. package/dist/ionic/{p-84236acb.entry.js → p-b92a19c8.entry.js} +1 -1
  41. package/dist/ionic/{p-9c6fddc6.entry.js → p-e206b074.entry.js} +1 -1
  42. package/dist/ionic/{p-8b54aa01.entry.js → p-e61fd4b2.entry.js} +1 -1
  43. package/dist/ionic/{p-07d8f62a.entry.js → p-eb9b64a6.entry.js} +1 -1
  44. package/dist/ionic/{p-7ed24ba0.entry.js → p-ef5372b6.entry.js} +1 -1
  45. package/dist/ionic/p-ly6Zj1CK.js +4 -0
  46. package/hydrate/index.js +119 -2
  47. package/hydrate/index.mjs +119 -2
  48. package/package.json +1 -1
  49. package/dist/ionic/p-C3MD7jSK.js +0 -4
  50. package/dist/ionic/p-ac2be9d6.entry.js +0 -4
@@ -182,6 +182,14 @@ const Refresher = /*@__PURE__*/ proxyCustomElement(class Refresher extends HTMLE
182
182
  this.beginRefresh();
183
183
  this.didRefresh = true;
184
184
  hapticImpact({ style: ImpactStyle.Light });
185
+ /**
186
+ * Clear focus from any active element to prevent scroll jumps
187
+ * when the refresh completes
188
+ */
189
+ const activeElement = document.activeElement;
190
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
191
+ activeElement.blur();
192
+ }
185
193
  /**
186
194
  * Translate the content element otherwise when pointer is removed
187
195
  * from screen the scroll content will bounce back over the refresher
@@ -597,6 +605,15 @@ const Refresher = /*@__PURE__*/ proxyCustomElement(class Refresher extends HTMLE
597
605
  this.state = 8 /* RefresherState.Refreshing */;
598
606
  // place the content in a hangout position while it thinks
599
607
  this.setCss(this.pullMin, this.snapbackDuration, true, '');
608
+ /**
609
+ * Clear focus from any active element to prevent the browser
610
+ * from restoring focus and causing scroll jumps after refresh.
611
+ * This ensures the view stays at the top after refresh completes.
612
+ */
613
+ const activeElement = document.activeElement;
614
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
615
+ activeElement.blur();
616
+ }
600
617
  // emit "refresh" because it was pulled down far enough
601
618
  // and they let go to begin refreshing
602
619
  this.ionRefresh.emit({
@@ -679,7 +696,7 @@ const Refresher = /*@__PURE__*/ proxyCustomElement(class Refresher extends HTMLE
679
696
  }
680
697
  render() {
681
698
  const mode = getIonMode(this);
682
- return (h(Host, { key: '8c7a5cc32da02a9cbeaa954258148683f60a6d1b', slot: "fixed", class: {
699
+ return (h(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
683
700
  [mode]: true,
684
701
  // Used internally for styling
685
702
  [`refresher-${mode}`]: true,
@@ -5,7 +5,7 @@ import { d as doc } from './index9.js';
5
5
  import { h as focusVisibleElement, c as componentOnReady, a as addEventListener, b as removeEventListener, g as getElementRoot } from './helpers.js';
6
6
  import { OVERLAY_BACK_BUTTON_PRIORITY, shouldUseCloseWatcher } from './hardware-back-button.js';
7
7
  import { c as config, a as printIonError, p as printIonWarning } from './index4.js';
8
- import { b as getIonMode } from './ionic-global.js';
8
+ import { b as getIonMode, a as isPlatform } from './ionic-global.js';
9
9
  import { C as CoreDelegate } from './framework-delegate.js';
10
10
  import { B as BACKDROP_NO_SCROLL } from './gesture-controller.js';
11
11
 
@@ -508,6 +508,7 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
508
508
  if (overlay.presented) {
509
509
  return;
510
510
  }
511
+ console.log("presenting overlay...");
511
512
  /**
512
513
  * Due to accessibility guidelines, toasts do not have
513
514
  * focus traps.
@@ -517,8 +518,9 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
517
518
  */
518
519
  if (overlay.el.tagName !== 'ION-TOAST') {
519
520
  setRootAriaHidden(true);
520
- document.body.classList.add(BACKDROP_NO_SCROLL);
521
521
  }
522
+ hideUnderlyingOverlaysFromScreenReaders(overlay.el);
523
+ hideAnimatingOverlayFromScreenReaders(overlay.el);
522
524
  overlay.presented = true;
523
525
  overlay.willPresent.emit();
524
526
  (_a = overlay.willPresentShorthand) === null || _a === void 0 ? void 0 : _a.emit();
@@ -565,6 +567,7 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
565
567
  * screen readers.
566
568
  */
567
569
  overlay.el.removeAttribute('aria-hidden');
570
+ overlay.el.removeAttribute('inert');
568
571
  };
569
572
  /**
570
573
  * When an overlay component is dismissed,
@@ -644,6 +647,12 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
644
647
  }
645
648
  overlay.presented = false;
646
649
  try {
650
+ /**
651
+ * There is no need to show the overlay to screen readers during
652
+ * the dismiss animation. This is because the overlay will be removed
653
+ * from the DOM after the animation is complete.
654
+ */
655
+ hideAnimatingOverlayFromScreenReaders(overlay.el);
647
656
  // Overlay contents should not be clickable during dismiss
648
657
  overlay.el.style.setProperty('pointer-events', 'none');
649
658
  overlay.willDismiss.emit({ data, role });
@@ -682,6 +691,7 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
682
691
  printIonError(`[${overlay.el.tagName.toLowerCase()}] - `, err);
683
692
  }
684
693
  overlay.el.remove();
694
+ revealOverlaysToScreenReaders();
685
695
  return true;
686
696
  };
687
697
  const getAppRoot = (doc) => {
@@ -877,6 +887,96 @@ const createTriggerController = () => {
877
887
  removeClickListener,
878
888
  };
879
889
  };
890
+ /**
891
+ * The overlay that is being animated also needs to hide from screen
892
+ * readers during its animation. This ensures that assistive technologies
893
+ * like TalkBack do not announce or interact with the content until the
894
+ * animation is complete, avoiding confusion for users.
895
+ *
896
+ * When the overlay is presented on an Android device, TalkBack's focus rings
897
+ * may appear in the wrong position due to the transition (specifically
898
+ * `transform` styles). This occurs because the focus rings are initially
899
+ * displayed at the starting position of the elements before the transition
900
+ * begins. This workaround ensures the focus rings do not appear in the
901
+ * incorrect location.
902
+ *
903
+ * If this solution is applied to iOS devices, then it leads to a bug where
904
+ * the overlays cannot be accessed by screen readers. This is due to
905
+ * VoiceOver not being able to update the accessibility tree when the
906
+ * `aria-hidden` is removed.
907
+ *
908
+ * @param overlay - The overlay that is being animated.
909
+ */
910
+ const hideAnimatingOverlayFromScreenReaders = (overlay) => {
911
+ if (doc === undefined)
912
+ return;
913
+ if (isPlatform('android')) {
914
+ /**
915
+ * Once the animation is complete, this attribute will be removed.
916
+ * This is done at the end of the `present` method.
917
+ */
918
+ overlay.setAttribute('aria-hidden', 'true');
919
+ overlay.setAttribute('inert', '');
920
+ }
921
+ };
922
+ /**
923
+ * Ensure that underlying overlays have aria-hidden if necessary so that screen readers
924
+ * cannot move focus to these elements. Note that we cannot rely on focus/focusin/focusout
925
+ * events here because those events do not fire when the screen readers moves to a non-focusable
926
+ * element such as text.
927
+ * Without this logic screen readers would be able to move focus outside of the top focus-trapped overlay.
928
+ *
929
+ * @param newTopMostOverlay - The overlay that is being presented. Since the overlay has not been
930
+ * fully presented yet at the time this function is called it will not be included in the getPresentedOverlays result.
931
+ */
932
+ const hideUnderlyingOverlaysFromScreenReaders = (newTopMostOverlay) => {
933
+ var _a;
934
+ if (doc === undefined)
935
+ return;
936
+ const overlays = getPresentedOverlays(doc);
937
+ for (let i = overlays.length - 1; i >= 0; i--) {
938
+ const presentedOverlay = overlays[i];
939
+ const nextPresentedOverlay = (_a = overlays[i + 1]) !== null && _a !== void 0 ? _a : newTopMostOverlay;
940
+ /**
941
+ * If next overlay has aria-hidden then all remaining overlays will have it too.
942
+ * Or, if the next overlay is a Toast that does not have aria-hidden then current overlay
943
+ * should not have aria-hidden either so focus can remain in the current overlay.
944
+ */
945
+ if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') {
946
+ presentedOverlay.setAttribute('aria-hidden', 'true');
947
+ presentedOverlay.setAttribute('inert', '');
948
+ }
949
+ }
950
+ };
951
+ /**
952
+ * When dismissing an overlay we need to reveal the new top-most overlay to screen readers.
953
+ * If the top-most overlay is a Toast we potentially need to reveal more overlays since
954
+ * focus is never automatically moved to the Toast.
955
+ */
956
+ const revealOverlaysToScreenReaders = () => {
957
+ if (doc === undefined)
958
+ return;
959
+ const overlays = getPresentedOverlays(doc);
960
+ for (let i = overlays.length - 1; i >= 0; i--) {
961
+ const currentOverlay = overlays[i];
962
+ /**
963
+ * If the current we are looking at is a Toast then we can remove aria-hidden.
964
+ * However, we potentially need to keep looking at the overlay stack because there
965
+ * could be more Toasts underneath. Additionally, we need to unhide the closest non-Toast
966
+ * overlay too so focus can move there since focus is never automatically moved to the Toast.
967
+ */
968
+ currentOverlay.removeAttribute('aria-hidden');
969
+ currentOverlay.removeAttribute('inert');
970
+ /**
971
+ * If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely
972
+ * since this overlay should always receive focus. As a result, all underlying overlays should still
973
+ * be hidden from screen readers.
974
+ */
975
+ if (currentOverlay.tagName !== 'ION-TOAST') {
976
+ break;
977
+ }
978
+ }
979
+ };
880
980
  const FOCUS_TRAP_DISABLE_CLASS = 'ion-disable-focus-trap';
881
981
 
882
982
  export { BACKDROP as B, FOCUS_TRAP_DISABLE_CLASS as F, GESTURE as G, OVERLAY_GESTURE_PRIORITY as O, alertController as a, actionSheetController as b, popoverController as c, createDelegateController as d, createTriggerController as e, present as f, dismiss as g, eventMethod as h, isCancel as i, prepareOverlay as j, setOverlayId as k, loadingController as l, modalController as m, focusFirstDescendant as n, getPresentedOverlay as o, pickerController as p, focusLastDescendant as q, safeCall as s, toastController as t };
@@ -15,7 +15,7 @@ var index$2 = require('./index-DNh170BW.js');
15
15
  var config = require('./config-CKhELRRu.js');
16
16
  var theme = require('./theme-CeDs6Hcv.js');
17
17
  var index$3 = require('./index-D24wggHR.js');
18
- var overlays = require('./overlays-DUsEBICv.js');
18
+ var overlays = require('./overlays-DFkeeMZX.js');
19
19
  require('./index-DkNv4J_i.js');
20
20
  require('./gesture-controller-dtqlP_q4.js');
21
21
  require('./hardware-back-button-BxdNu76F.js');
@@ -7,7 +7,7 @@ var index = require('./index-DNh170BW.js');
7
7
  var buttonActive = require('./button-active-BzZenWWH.js');
8
8
  var helpers = require('./helpers-DgwmcYAu.js');
9
9
  var lockController = require('./lock-controller-aDB9wrEf.js');
10
- var overlays = require('./overlays-DUsEBICv.js');
10
+ var overlays = require('./overlays-DFkeeMZX.js');
11
11
  var theme = require('./theme-CeDs6Hcv.js');
12
12
  var ionicGlobal = require('./ionic-global-UI5YPSi-.js');
13
13
  var animation = require('./animation-ZJ1lAkZD.js');
@@ -8,7 +8,7 @@ var config = require('./config-CKhELRRu.js');
8
8
  var buttonActive = require('./button-active-BzZenWWH.js');
9
9
  var helpers = require('./helpers-DgwmcYAu.js');
10
10
  var lockController = require('./lock-controller-aDB9wrEf.js');
11
- var overlays = require('./overlays-DUsEBICv.js');
11
+ var overlays = require('./overlays-DFkeeMZX.js');
12
12
  var theme = require('./theme-CeDs6Hcv.js');
13
13
  var ionicGlobal = require('./ionic-global-UI5YPSi-.js');
14
14
  var animation = require('./animation-ZJ1lAkZD.js');
@@ -6,7 +6,7 @@
6
6
  var index = require('./index-DNh170BW.js');
7
7
  var focusVisible = require('./focus-visible-CCvKiLh3.js');
8
8
  var helpers = require('./helpers-DgwmcYAu.js');
9
- var overlays = require('./overlays-DUsEBICv.js');
9
+ var overlays = require('./overlays-DFkeeMZX.js');
10
10
  var dir = require('./dir-Cn0z1rJH.js');
11
11
  var theme = require('./theme-CeDs6Hcv.js');
12
12
  var index$1 = require('./index-DqmRDbxg.js');
@@ -7,7 +7,7 @@ var index = require('./index-DNh170BW.js');
7
7
  var config = require('./config-CKhELRRu.js');
8
8
  var helpers = require('./helpers-DgwmcYAu.js');
9
9
  var lockController = require('./lock-controller-aDB9wrEf.js');
10
- var overlays = require('./overlays-DUsEBICv.js');
10
+ var overlays = require('./overlays-DFkeeMZX.js');
11
11
  var theme = require('./theme-CeDs6Hcv.js');
12
12
  var ionicGlobal = require('./ionic-global-UI5YPSi-.js');
13
13
  var animation = require('./animation-ZJ1lAkZD.js');
@@ -5,7 +5,7 @@
5
5
 
6
6
  var index = require('./index-DNh170BW.js');
7
7
  var cubicBezier = require('./cubic-bezier-DAjy1V-e.js');
8
- var overlays = require('./overlays-DUsEBICv.js');
8
+ var overlays = require('./overlays-DFkeeMZX.js');
9
9
  var gestureController = require('./gesture-controller-dtqlP_q4.js');
10
10
  var hardwareBackButton = require('./hardware-back-button-BxdNu76F.js');
11
11
  var helpers = require('./helpers-DgwmcYAu.js');
@@ -9,7 +9,7 @@ var frameworkDelegate = require('./framework-delegate-WkyjrnCx.js');
9
9
  var helpers = require('./helpers-DgwmcYAu.js');
10
10
  var lockController = require('./lock-controller-aDB9wrEf.js');
11
11
  var capacitor = require('./capacitor-DmA66EwP.js');
12
- var overlays = require('./overlays-DUsEBICv.js');
12
+ var overlays = require('./overlays-DFkeeMZX.js');
13
13
  var theme = require('./theme-CeDs6Hcv.js');
14
14
  var index$4 = require('./index-BzEyuIww.js');
15
15
  var ionicGlobal = require('./ionic-global-UI5YPSi-.js');
@@ -4,7 +4,7 @@
4
4
  'use strict';
5
5
 
6
6
  var index = require('./index-DNh170BW.js');
7
- var overlays = require('./overlays-DUsEBICv.js');
7
+ var overlays = require('./overlays-DFkeeMZX.js');
8
8
  var frameworkDelegate = require('./framework-delegate-WkyjrnCx.js');
9
9
  var helpers = require('./helpers-DgwmcYAu.js');
10
10
  var lockController = require('./lock-controller-aDB9wrEf.js');
@@ -375,6 +375,14 @@ const Refresher = class {
375
375
  this.beginRefresh();
376
376
  this.didRefresh = true;
377
377
  haptic.hapticImpact({ style: haptic.ImpactStyle.Light });
378
+ /**
379
+ * Clear focus from any active element to prevent scroll jumps
380
+ * when the refresh completes
381
+ */
382
+ const activeElement = document.activeElement;
383
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
384
+ activeElement.blur();
385
+ }
378
386
  /**
379
387
  * Translate the content element otherwise when pointer is removed
380
388
  * from screen the scroll content will bounce back over the refresher
@@ -790,6 +798,15 @@ const Refresher = class {
790
798
  this.state = 8 /* RefresherState.Refreshing */;
791
799
  // place the content in a hangout position while it thinks
792
800
  this.setCss(this.pullMin, this.snapbackDuration, true, '');
801
+ /**
802
+ * Clear focus from any active element to prevent the browser
803
+ * from restoring focus and causing scroll jumps after refresh.
804
+ * This ensures the view stays at the top after refresh completes.
805
+ */
806
+ const activeElement = document.activeElement;
807
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
808
+ activeElement.blur();
809
+ }
793
810
  // emit "refresh" because it was pulled down far enough
794
811
  // and they let go to begin refreshing
795
812
  this.ionRefresh.emit({
@@ -872,7 +889,7 @@ const Refresher = class {
872
889
  }
873
890
  render() {
874
891
  const mode = ionicGlobal.getIonMode(this);
875
- return (index.h(index.Host, { key: '8c7a5cc32da02a9cbeaa954258148683f60a6d1b', slot: "fixed", class: {
892
+ return (index.h(index.Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
876
893
  [mode]: true,
877
894
  // Used internally for styling
878
895
  [`refresher-${mode}`]: true,
@@ -5,7 +5,7 @@
5
5
 
6
6
  var index = require('./index-DNh170BW.js');
7
7
  var ionicGlobal = require('./ionic-global-UI5YPSi-.js');
8
- var overlays = require('./overlays-DUsEBICv.js');
8
+ var overlays = require('./overlays-DFkeeMZX.js');
9
9
  var theme = require('./theme-CeDs6Hcv.js');
10
10
  require('./index-DkNv4J_i.js');
11
11
  require('./helpers-DgwmcYAu.js');
@@ -7,7 +7,7 @@ var index = require('./index-DNh170BW.js');
7
7
  var notchController = require('./notch-controller-Bf5Rr4R5.js');
8
8
  var compareWithUtils = require('./compare-with-utils-DSicavqM.js');
9
9
  var helpers = require('./helpers-DgwmcYAu.js');
10
- var overlays = require('./overlays-DUsEBICv.js');
10
+ var overlays = require('./overlays-DFkeeMZX.js');
11
11
  var dir = require('./dir-Cn0z1rJH.js');
12
12
  var theme = require('./theme-CeDs6Hcv.js');
13
13
  var watchOptions = require('./watch-options-CviOsrTS.js');
@@ -7,7 +7,7 @@ var index$1 = require('./index-DNh170BW.js');
7
7
  var config = require('./config-CKhELRRu.js');
8
8
  var helpers = require('./helpers-DgwmcYAu.js');
9
9
  var lockController = require('./lock-controller-aDB9wrEf.js');
10
- var overlays = require('./overlays-DUsEBICv.js');
10
+ var overlays = require('./overlays-DFkeeMZX.js');
11
11
  var theme = require('./theme-CeDs6Hcv.js');
12
12
  var ionicGlobal = require('./ionic-global-UI5YPSi-.js');
13
13
  var animation = require('./animation-ZJ1lAkZD.js');
@@ -510,6 +510,7 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
510
510
  if (overlay.presented) {
511
511
  return;
512
512
  }
513
+ console.log("presenting overlay...");
513
514
  /**
514
515
  * Due to accessibility guidelines, toasts do not have
515
516
  * focus traps.
@@ -519,8 +520,9 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
519
520
  */
520
521
  if (overlay.el.tagName !== 'ION-TOAST') {
521
522
  setRootAriaHidden(true);
522
- document.body.classList.add(gestureController.BACKDROP_NO_SCROLL);
523
523
  }
524
+ hideUnderlyingOverlaysFromScreenReaders(overlay.el);
525
+ hideAnimatingOverlayFromScreenReaders(overlay.el);
524
526
  overlay.presented = true;
525
527
  overlay.willPresent.emit();
526
528
  (_a = overlay.willPresentShorthand) === null || _a === void 0 ? void 0 : _a.emit();
@@ -567,6 +569,7 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
567
569
  * screen readers.
568
570
  */
569
571
  overlay.el.removeAttribute('aria-hidden');
572
+ overlay.el.removeAttribute('inert');
570
573
  };
571
574
  /**
572
575
  * When an overlay component is dismissed,
@@ -646,6 +649,12 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
646
649
  }
647
650
  overlay.presented = false;
648
651
  try {
652
+ /**
653
+ * There is no need to show the overlay to screen readers during
654
+ * the dismiss animation. This is because the overlay will be removed
655
+ * from the DOM after the animation is complete.
656
+ */
657
+ hideAnimatingOverlayFromScreenReaders(overlay.el);
649
658
  // Overlay contents should not be clickable during dismiss
650
659
  overlay.el.style.setProperty('pointer-events', 'none');
651
660
  overlay.willDismiss.emit({ data, role });
@@ -684,6 +693,7 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
684
693
  index.printIonError(`[${overlay.el.tagName.toLowerCase()}] - `, err);
685
694
  }
686
695
  overlay.el.remove();
696
+ revealOverlaysToScreenReaders();
687
697
  return true;
688
698
  };
689
699
  const getAppRoot = (doc) => {
@@ -879,6 +889,96 @@ const createTriggerController = () => {
879
889
  removeClickListener,
880
890
  };
881
891
  };
892
+ /**
893
+ * The overlay that is being animated also needs to hide from screen
894
+ * readers during its animation. This ensures that assistive technologies
895
+ * like TalkBack do not announce or interact with the content until the
896
+ * animation is complete, avoiding confusion for users.
897
+ *
898
+ * When the overlay is presented on an Android device, TalkBack's focus rings
899
+ * may appear in the wrong position due to the transition (specifically
900
+ * `transform` styles). This occurs because the focus rings are initially
901
+ * displayed at the starting position of the elements before the transition
902
+ * begins. This workaround ensures the focus rings do not appear in the
903
+ * incorrect location.
904
+ *
905
+ * If this solution is applied to iOS devices, then it leads to a bug where
906
+ * the overlays cannot be accessed by screen readers. This is due to
907
+ * VoiceOver not being able to update the accessibility tree when the
908
+ * `aria-hidden` is removed.
909
+ *
910
+ * @param overlay - The overlay that is being animated.
911
+ */
912
+ const hideAnimatingOverlayFromScreenReaders = (overlay) => {
913
+ if (index$1.doc === undefined)
914
+ return;
915
+ if (ionicGlobal.isPlatform('android')) {
916
+ /**
917
+ * Once the animation is complete, this attribute will be removed.
918
+ * This is done at the end of the `present` method.
919
+ */
920
+ overlay.setAttribute('aria-hidden', 'true');
921
+ overlay.setAttribute('inert', '');
922
+ }
923
+ };
924
+ /**
925
+ * Ensure that underlying overlays have aria-hidden if necessary so that screen readers
926
+ * cannot move focus to these elements. Note that we cannot rely on focus/focusin/focusout
927
+ * events here because those events do not fire when the screen readers moves to a non-focusable
928
+ * element such as text.
929
+ * Without this logic screen readers would be able to move focus outside of the top focus-trapped overlay.
930
+ *
931
+ * @param newTopMostOverlay - The overlay that is being presented. Since the overlay has not been
932
+ * fully presented yet at the time this function is called it will not be included in the getPresentedOverlays result.
933
+ */
934
+ const hideUnderlyingOverlaysFromScreenReaders = (newTopMostOverlay) => {
935
+ var _a;
936
+ if (index$1.doc === undefined)
937
+ return;
938
+ const overlays = getPresentedOverlays(index$1.doc);
939
+ for (let i = overlays.length - 1; i >= 0; i--) {
940
+ const presentedOverlay = overlays[i];
941
+ const nextPresentedOverlay = (_a = overlays[i + 1]) !== null && _a !== void 0 ? _a : newTopMostOverlay;
942
+ /**
943
+ * If next overlay has aria-hidden then all remaining overlays will have it too.
944
+ * Or, if the next overlay is a Toast that does not have aria-hidden then current overlay
945
+ * should not have aria-hidden either so focus can remain in the current overlay.
946
+ */
947
+ if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') {
948
+ presentedOverlay.setAttribute('aria-hidden', 'true');
949
+ presentedOverlay.setAttribute('inert', '');
950
+ }
951
+ }
952
+ };
953
+ /**
954
+ * When dismissing an overlay we need to reveal the new top-most overlay to screen readers.
955
+ * If the top-most overlay is a Toast we potentially need to reveal more overlays since
956
+ * focus is never automatically moved to the Toast.
957
+ */
958
+ const revealOverlaysToScreenReaders = () => {
959
+ if (index$1.doc === undefined)
960
+ return;
961
+ const overlays = getPresentedOverlays(index$1.doc);
962
+ for (let i = overlays.length - 1; i >= 0; i--) {
963
+ const currentOverlay = overlays[i];
964
+ /**
965
+ * If the current we are looking at is a Toast then we can remove aria-hidden.
966
+ * However, we potentially need to keep looking at the overlay stack because there
967
+ * could be more Toasts underneath. Additionally, we need to unhide the closest non-Toast
968
+ * overlay too so focus can move there since focus is never automatically moved to the Toast.
969
+ */
970
+ currentOverlay.removeAttribute('aria-hidden');
971
+ currentOverlay.removeAttribute('inert');
972
+ /**
973
+ * If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely
974
+ * since this overlay should always receive focus. As a result, all underlying overlays should still
975
+ * be hidden from screen readers.
976
+ */
977
+ if (currentOverlay.tagName !== 'ION-TOAST') {
978
+ break;
979
+ }
980
+ }
981
+ };
882
982
  const FOCUS_TRAP_DISABLE_CLASS = 'ion-disable-focus-trap';
883
983
 
884
984
  exports.BACKDROP = BACKDROP;
@@ -175,6 +175,14 @@ export class Refresher {
175
175
  this.beginRefresh();
176
176
  this.didRefresh = true;
177
177
  hapticImpact({ style: ImpactStyle.Light });
178
+ /**
179
+ * Clear focus from any active element to prevent scroll jumps
180
+ * when the refresh completes
181
+ */
182
+ const activeElement = document.activeElement;
183
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
184
+ activeElement.blur();
185
+ }
178
186
  /**
179
187
  * Translate the content element otherwise when pointer is removed
180
188
  * from screen the scroll content will bounce back over the refresher
@@ -590,6 +598,15 @@ export class Refresher {
590
598
  this.state = 8 /* RefresherState.Refreshing */;
591
599
  // place the content in a hangout position while it thinks
592
600
  this.setCss(this.pullMin, this.snapbackDuration, true, '');
601
+ /**
602
+ * Clear focus from any active element to prevent the browser
603
+ * from restoring focus and causing scroll jumps after refresh.
604
+ * This ensures the view stays at the top after refresh completes.
605
+ */
606
+ const activeElement = document.activeElement;
607
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
608
+ activeElement.blur();
609
+ }
593
610
  // emit "refresh" because it was pulled down far enough
594
611
  // and they let go to begin refreshing
595
612
  this.ionRefresh.emit({
@@ -672,7 +689,7 @@ export class Refresher {
672
689
  }
673
690
  render() {
674
691
  const mode = getIonMode(this);
675
- return (h(Host, { key: '8c7a5cc32da02a9cbeaa954258148683f60a6d1b', slot: "fixed", class: {
692
+ return (h(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
676
693
  [mode]: true,
677
694
  // Used internally for styling
678
695
  [`refresher-${mode}`]: true,