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

Function useRestoreFocus

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

Source from the content-addressed store, hash-verified

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