MCPcopy Create free account
hub / github.com/adobe/react-spectrum / useRestoreFocus

Function useRestoreFocus

packages/react-aria/src/focus/FocusScope.tsx:626–824  ·  view source on GitHub ↗
(
  scopeRef: RefObject<Element[] | null>,
  restoreFocus?: boolean,
  contain?: boolean
)

Source from the content-addressed store, hash-verified

624}
625
626function useRestoreFocus(
627 scopeRef: RefObject<Element[] | null>,
628 restoreFocus?: boolean,
629 contain?: boolean
630) {
631 // create a ref during render instead of useLayoutEffect so the active element is saved before a child with autoFocus=true mounts.
632
633 const nodeToRestoreRef = useRef(
634 typeof document !== 'undefined'
635 ? (getActiveElement(
636 // oxlint-disable-next-line react/react-compiler
637 getOwnerDocument(scopeRef.current ? scopeRef.current[0] : undefined)
638 ) as FocusableElement)
639 : null
640 );
641
642 // restoring scopes should all track if they are active regardless of contain, but contain already tracks it plus logic to contain the focus
643 // restoring-non-containing scopes should only care if they become active so they can perform the restore
644 useLayoutEffect(() => {
645 let scope = scopeRef.current;
646 const ownerDocument = getOwnerDocument(scope ? scope[0] : undefined);
647 if (!restoreFocus || contain) {
648 return;
649 }
650
651 let onFocus = () => {
652 // If focusing an element in a child scope of the currently active scope, the child becomes active.
653 // Moving out of the active scope to an ancestor is not allowed.
654 if (
655 (!activeScope || isAncestorScope(activeScope, scopeRef)) &&
656 isElementInScope(getActiveElement(ownerDocument), scopeRef.current)
657 ) {
658 activeScope = scopeRef;
659 }
660 };
661
662 ownerDocument.addEventListener('focusin', onFocus, false);
663 scope?.forEach(element => element.addEventListener('focusin', onFocus, false));
664 return () => {
665 ownerDocument.removeEventListener('focusin', onFocus, false);
666 scope?.forEach(element => element.removeEventListener('focusin', onFocus, false));
667 };
668 // eslint-disable-next-line react-hooks/exhaustive-deps
669 }, [scopeRef, contain]);
670
671 useLayoutEffect(() => {
672 const ownerDocument = getOwnerDocument(scopeRef.current ? scopeRef.current[0] : undefined);
673
674 if (!restoreFocus) {
675 return;
676 }
677
678 // Handle the Tab key so that tabbing out of the scope goes to the next element
679 // after the node that had focus when the scope mounted. This is important when
680 // using portals for overlays, so that focus goes to the expected element when
681 // tabbing out of the overlay.
682 let onKeyDown = (e: KeyboardEvent) => {
683 if (

Callers 1

FocusScopeFunction · 0.85

Calls 10

getActiveElementFunction · 0.90
getOwnerDocumentFunction · 0.90
isElementInChildScopeFunction · 0.85
shouldRestoreFocusFunction · 0.85
restoreFocusToElementFunction · 0.85
getFirstInScopeFunction · 0.85
addEventListenerMethod · 0.80
removeEventListenerMethod · 0.80
getTreeNodeMethod · 0.80
cloneMethod · 0.65

Tested by

no test coverage detected