@@ -3,14 +3,14 @@ import { useRef } from 'react';
33import useWindow from './useWindow.js' ;
44import useMounted from '@restart/hooks/useMounted' ;
55import useEventCallback from '@restart/hooks/useEventCallback' ;
6- import { getTabbableElements } from './tabbable.js' ;
6+ import { getTabbableElements , getTabbableElementsOrSelf } from './tabbable.js' ;
77import activeElement from 'dom-helpers/activeElement' ;
88
99export function useFocusTrap ( {
10- container ,
10+ getContainer ,
1111 disabled,
1212} : {
13- container : HTMLElement ;
13+ getContainer : ( ) => HTMLElement | null ;
1414 disabled ?: ( ) => boolean ;
1515} ) {
1616 const ownerWindow = useWindow ( ) ;
@@ -19,50 +19,29 @@ export function useFocusTrap({
1919 const listenersRef = useRef ( new Set < ( ...args : any [ ] ) => void > ( ) ) ;
2020
2121 const handleKeydown = useEventCallback ( ( event : KeyboardEvent ) => {
22+ const container = getContainer ( ) ;
23+
2224 if ( event . key !== 'Tab' || ! container ) {
2325 return ;
2426 }
2527
26- const tabbables = getTabbableElements ( container ) ;
27- const currentActiveElement = activeElement ( ownerWindow ?. document ) ;
28-
29- const isTabbingBackwards =
30- currentActiveElement === tabbables [ 0 ] && event . shiftKey ;
28+ const tabbables = getTabbableElementsOrSelf ( container ) ;
3129
32- const isTabbingForward =
33- currentActiveElement == = tabbables [ tabbables . length - 1 ] ;
30+ const firstTabbable = tabbables [ 0 ] ;
31+ const lastTabbable = tabbables [ tabbables . length - 1 ] ;
3432
35- if ( ! isTabbingBackwards && ! isTabbingForward ) {
33+ if ( event . shiftKey && event . target === tabbables [ 0 ] ) {
34+ lastTabbable ?. focus ( ) ;
35+ event . preventDefault ( ) ;
3636 return ;
3737 }
3838
39- /**
40- * We want focus to move from the focus trapped container out of the document like
41- * it would if you tabbed from the end or start of the page.
42- *
43- * Generally this Just Works for tabbing forward out of the modal, as modals are often
44- * the last element in the document. In cases where it isn't or if you tabbed backwards
45- * we need to allow focus to move out of the document.
46- *
47- * This is done by way of a little "trick". `tab` events happen before focus moves so
48- * you can shift the focus to a new element and the tab will then move focus forward or backwards
49- * depending on the direction. We take advantage of this by moving focus to the first or last tabbable element
50- * in the document.
51- */
52- const bodyTabbables = getTabbableElements (
53- ownerWindow ?. document ?? document ! ,
54- ) ;
55-
56- if ( isTabbingBackwards ) {
57- if ( bodyTabbables [ 0 ] !== currentActiveElement ) {
58- bodyTabbables [ 0 ] ?. focus ( ) ;
59- }
60- } else if ( isTabbingForward ) {
61- const lastTabbable = bodyTabbables [ bodyTabbables . length - 1 ] ;
62-
63- if ( lastTabbable !== currentActiveElement ) {
64- lastTabbable ?. focus ( ) ;
65- }
39+ if (
40+ ( ! event . shiftKey && event . target === lastTabbable ) ||
41+ ! container . contains ( event . target as Element )
42+ ) {
43+ firstTabbable ?. focus ( ) ;
44+ event . preventDefault ( ) ;
6645 }
6746 } ) ;
6847
@@ -71,16 +50,17 @@ export function useFocusTrap({
7150 return ;
7251 }
7352
53+ const container = getContainer ( ) ;
7454 const currentActiveElement = activeElement ( ownerWindow ?. document ) ;
7555
7656 if (
7757 container &&
7858 currentActiveElement &&
7959 ! container . contains ( currentActiveElement )
8060 ) {
81- const tabbables = getTabbableElements ( container ) ;
61+ const tabbables = getTabbableElementsOrSelf ( container ) ;
8262
83- ( tabbables [ 0 ] ?? container ) . focus ( ) ;
63+ tabbables [ 0 ] ? .focus ( ) ;
8464 }
8565 } ) ;
8666
@@ -92,22 +72,26 @@ export function useFocusTrap({
9272 }
9373
9474 ownerWindow . addEventListener ( 'focus' , handleFocus , { capture : true } ) ;
75+ ownerWindow . addEventListener ( 'blur' , handleBlur ) ;
9576 document . addEventListener ( 'keydown' , handleKeydown ) ;
9677
97- listenersRef . current . add ( ( ) =>
98- ownerWindow . removeEventListener ( 'focus' , handleFocus , { capture : true } ) ,
99- ) ;
100- listenersRef . current . add ( ( ) =>
101- document . removeEventListener ( 'keydown' , handleKeydown ) ,
102- ) ;
78+ listenersRef . current . add ( ( ) => {
79+ ownerWindow . removeEventListener ( 'focus' , handleFocus , { capture : true } ) ;
80+ ownerWindow . removeEventListener ( 'blur' , handleBlur ) ;
81+ document . removeEventListener ( 'keydown' , handleKeydown ) ;
82+ } ) ;
10383
10484 function handleFocus ( event : FocusEvent ) {
10585 console . log ( 'handleFocus' , event . target ) ;
10686 // the timeout is necessary b/c this will run before the new modal is mounted
10787 // and so steals focus from it
10888 setTimeout ( ( ) => handleEnforceFocus ( event ) ) ;
10989 }
110- } , [ container , handleEnforceFocus ] ) ;
90+
91+ function handleBlur ( event : FocusEvent ) {
92+ console . log ( 'handleBlur' , event . target ) ;
93+ }
94+ } , [ handleEnforceFocus ] ) ;
11195
11296 const stop = useCallback ( ( ) => {
11397 listenersRef . current . forEach ( ( listener ) => listener ( ) ) ;
0 commit comments