|
12 | 12 |
|
13 | 13 | import {FocusableElement, RefObject} from '@react-types/shared';
|
14 | 14 | import {focusSafely} from './focusSafely';
|
15 |
| -import {getOwnerDocument, useLayoutEffect} from '@react-aria/utils'; |
| 15 | +import {getInteractionModality} from '@react-aria/interactions'; |
| 16 | +import {getOwnerDocument, isAndroid, isChrome, useLayoutEffect} from '@react-aria/utils'; |
16 | 17 | import {isElementVisible} from './isElementVisible';
|
17 | 18 | import React, {ReactNode, useContext, useEffect, useMemo, useRef} from 'react';
|
18 | 19 |
|
@@ -381,8 +382,14 @@ function useFocusContainment(scopeRef: RefObject<Element[] | null>, contain?: bo
|
381 | 382 | cancelAnimationFrame(raf.current);
|
382 | 383 | }
|
383 | 384 | raf.current = requestAnimationFrame(() => {
|
| 385 | + // Patches infinite focus coersion loop for Android Talkback where the user isn't able to move the virtual cursor |
| 386 | + // if within a containing focus scope. Bug filed against Chrome: https://issuetracker.google.com/issues/384844019. |
| 387 | + // Note that this means focus can leave focus containing modals due to this, but it is isolated to Chrome Talkback. |
| 388 | + let modality = getInteractionModality(); |
| 389 | + let shouldSkipFocusRestore = (modality === 'virtual' || modality === null) && isAndroid() && isChrome(); |
| 390 | + |
384 | 391 | // Use document.activeElement instead of e.relatedTarget so we can tell if user clicked into iframe
|
385 |
| - if (ownerDocument.activeElement && shouldContainFocus(scopeRef) && !isElementInChildScope(ownerDocument.activeElement, scopeRef)) { |
| 392 | + if (!shouldSkipFocusRestore && ownerDocument.activeElement && shouldContainFocus(scopeRef) && !isElementInChildScope(ownerDocument.activeElement, scopeRef)) { |
386 | 393 | activeScope = scopeRef;
|
387 | 394 | if (ownerDocument.body.contains(e.target)) {
|
388 | 395 | focusedNode.current = e.target;
|
|
0 commit comments