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