Skip to content

Commit 3dec35c

Browse files
committed
(overlay): clear unneeded code and fix focus behaviour moment
1 parent 157a113 commit 3dec35c

File tree

1 file changed

+15
-122
lines changed

1 file changed

+15
-122
lines changed

core/src/utils/overlays.ts

Lines changed: 15 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
getElementRoot,
3333
removeEventListener,
3434
} from './helpers';
35-
import { isPlatform } from './platform';
3635

3736
let lastOverlayIndex = 0;
3837
let lastId = 0;
@@ -513,6 +512,18 @@ export const present = async <OverlayPresentOptions>(
513512
return;
514513
}
515514

515+
/**
516+
* When an overlay that steals focus
517+
* is dismissed, focus should be returned
518+
* to the element that was focused
519+
* prior to the overlay opening. Toast
520+
* does not steal focus and is excluded
521+
* from returning focus as a result.
522+
*/
523+
if (overlay.el.tagName !== 'ION-TOAST') {
524+
restoreElementFocus(overlay.el);
525+
}
526+
516527
/**
517528
* Due to accessibility guidelines, toasts do not have
518529
* focus traps.
@@ -525,9 +536,6 @@ export const present = async <OverlayPresentOptions>(
525536
document.body.classList.add(BACKDROP_NO_SCROLL);
526537
}
527538

528-
hideUnderlyingOverlaysFromScreenReaders(overlay.el);
529-
hideAnimatingOverlayFromScreenReaders(overlay.el);
530-
531539
overlay.presented = true;
532540
overlay.willPresent.emit();
533541
overlay.willPresentShorthand?.emit();
@@ -544,18 +552,6 @@ export const present = async <OverlayPresentOptions>(
544552
overlay.didPresentShorthand?.emit();
545553
}
546554

547-
/**
548-
* When an overlay that steals focus
549-
* is dismissed, focus should be returned
550-
* to the element that was focused
551-
* prior to the overlay opening. Toast
552-
* does not steal focus and is excluded
553-
* from returning focus as a result.
554-
*/
555-
if (overlay.el.tagName !== 'ION-TOAST') {
556-
restoreElementFocus(overlay.el);
557-
}
558-
559555
/**
560556
* If the focused element is already
561557
* inside the overlay component then
@@ -598,6 +594,9 @@ const restoreElementFocus = async (overlayEl: any) => {
598594
return;
599595
}
600596

597+
// Ensure active element is blurred to prevent a11y warning issues
598+
previousElement.blur();
599+
601600
const shadowRoot = previousElement?.shadowRoot;
602601
if (shadowRoot) {
603602
// If there are no inner focusable elements, just focus the host element.
@@ -677,13 +676,6 @@ export const dismiss = async <OverlayDismissOptions>(
677676
overlay.presented = false;
678677

679678
try {
680-
/**
681-
* There is no need to show the overlay to screen readers during
682-
* the dismiss animation. This is because the overlay will be removed
683-
* from the DOM after the animation is complete.
684-
*/
685-
hideAnimatingOverlayFromScreenReaders(overlay.el);
686-
687679
// Overlay contents should not be clickable during dismiss
688680
overlay.el.style.setProperty('pointer-events', 'none');
689681
overlay.willDismiss.emit({ data, role });
@@ -731,8 +723,6 @@ export const dismiss = async <OverlayDismissOptions>(
731723

732724
overlay.el.remove();
733725

734-
revealOverlaysToScreenReaders();
735-
736726
return true;
737727
};
738728

@@ -969,101 +959,4 @@ export const createTriggerController = () => {
969959
};
970960
};
971961

972-
/**
973-
* The overlay that is being animated also needs to hide from screen
974-
* readers during its animation. This ensures that assistive technologies
975-
* like TalkBack do not announce or interact with the content until the
976-
* animation is complete, avoiding confusion for users.
977-
*
978-
* When the overlay is presented on an Android device, TalkBack's focus rings
979-
* may appear in the wrong position due to the transition (specifically
980-
* `transform` styles). This occurs because the focus rings are initially
981-
* displayed at the starting position of the elements before the transition
982-
* begins. This workaround ensures the focus rings do not appear in the
983-
* incorrect location.
984-
*
985-
* If this solution is applied to iOS devices, then it leads to a bug where
986-
* the overlays cannot be accessed by screen readers. This is due to
987-
* VoiceOver not being able to update the accessibility tree when the
988-
* `aria-hidden` is removed.
989-
*
990-
* @param overlay - The overlay that is being animated.
991-
*/
992-
const hideAnimatingOverlayFromScreenReaders = (overlay: HTMLIonOverlayElement) => {
993-
if (doc === undefined) return;
994-
995-
if (isPlatform('android')) {
996-
/**
997-
* Once the animation is complete, this attribute will be removed.
998-
* This is done at the end of the `present` method.
999-
*/
1000-
overlay.setAttribute('aria-hidden', 'true');
1001-
overlay.setAttribute('inert', '');
1002-
}
1003-
};
1004-
1005-
/**
1006-
* Ensure that underlying overlays have aria-hidden if necessary so that screen readers
1007-
* cannot move focus to these elements. Note that we cannot rely on focus/focusin/focusout
1008-
* events here because those events do not fire when the screen readers moves to a non-focusable
1009-
* element such as text.
1010-
* Without this logic screen readers would be able to move focus outside of the top focus-trapped overlay.
1011-
*
1012-
* @param newTopMostOverlay - The overlay that is being presented. Since the overlay has not been
1013-
* fully presented yet at the time this function is called it will not be included in the getPresentedOverlays result.
1014-
*/
1015-
const hideUnderlyingOverlaysFromScreenReaders = (newTopMostOverlay: HTMLIonOverlayElement) => {
1016-
if (doc === undefined) return;
1017-
1018-
const overlays = getPresentedOverlays(doc);
1019-
1020-
for (let i = overlays.length - 1; i >= 0; i--) {
1021-
const presentedOverlay = overlays[i];
1022-
const nextPresentedOverlay = overlays[i + 1] ?? newTopMostOverlay;
1023-
1024-
/**
1025-
* If next overlay has aria-hidden then all remaining overlays will have it too.
1026-
* Or, if the next overlay is a Toast that does not have aria-hidden then current overlay
1027-
* should not have aria-hidden either so focus can remain in the current overlay.
1028-
*/
1029-
if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') {
1030-
presentedOverlay.setAttribute('aria-hidden', 'true');
1031-
presentedOverlay.setAttribute('inert', '');
1032-
}
1033-
}
1034-
};
1035-
1036-
/**
1037-
* When dismissing an overlay we need to reveal the new top-most overlay to screen readers.
1038-
* If the top-most overlay is a Toast we potentially need to reveal more overlays since
1039-
* focus is never automatically moved to the Toast.
1040-
*/
1041-
const revealOverlaysToScreenReaders = () => {
1042-
if (doc === undefined) return;
1043-
1044-
const overlays = getPresentedOverlays(doc);
1045-
1046-
for (let i = overlays.length - 1; i >= 0; i--) {
1047-
const currentOverlay = overlays[i];
1048-
1049-
/**
1050-
* If the current we are looking at is a Toast then we can remove aria-hidden.
1051-
* However, we potentially need to keep looking at the overlay stack because there
1052-
* could be more Toasts underneath. Additionally, we need to unhide the closest non-Toast
1053-
* overlay too so focus can move there since focus is never automatically moved to the Toast.
1054-
*/
1055-
currentOverlay.removeAttribute('aria-hidden');
1056-
currentOverlay.removeAttribute('inert');
1057-
1058-
/**
1059-
* If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely
1060-
* since this overlay should always receive focus. As a result, all underlying overlays should still
1061-
* be hidden from screen readers.
1062-
*/
1063-
if (currentOverlay.tagName !== 'ION-TOAST') {
1064-
break;
1065-
}
1066-
}
1067-
};
1068-
1069962
export const FOCUS_TRAP_DISABLE_CLASS = 'ion-disable-focus-trap';

0 commit comments

Comments
 (0)