@@ -11,15 +11,63 @@ export function useFocusTrap({
1111 disabled,
1212} : {
1313 container : HTMLElement ;
14- disabled ?: boolean ;
14+ disabled ?: ( ) => boolean ;
1515} ) {
1616 const ownerWindow = useWindow ( ) ;
1717 const isMounted = useMounted ( ) ;
1818
1919 const listenersRef = useRef ( new Set < ( ...args : any [ ] ) => void > ( ) ) ;
2020
21+ const handleKeydown = useEventCallback ( ( event : KeyboardEvent ) => {
22+ if ( event . key !== 'Tab' || ! container ) {
23+ return ;
24+ }
25+
26+ const tabbables = getTabbableElements ( container ) ;
27+ const currentActiveElement = activeElement ( ownerWindow ?. document ) ;
28+
29+ const isTabbingBackwards =
30+ currentActiveElement === tabbables [ 0 ] && event . shiftKey ;
31+
32+ const isTabbingForward =
33+ currentActiveElement === tabbables [ tabbables . length - 1 ] ;
34+
35+ if ( ! isTabbingBackwards && ! isTabbingForward ) {
36+ return ;
37+ }
38+
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+ }
66+ }
67+ } ) ;
68+
2169 const handleEnforceFocus = useEventCallback ( ( _event : FocusEvent ) => {
22- if ( disabled ) {
70+ if ( disabled ?. ( ) ) {
2371 return ;
2472 }
2573
@@ -30,68 +78,31 @@ export function useFocusTrap({
3078 currentActiveElement &&
3179 ! container . contains ( currentActiveElement )
3280 ) {
33- container . focus ( ) ;
81+ const tabbables = getTabbableElements ( container ) ;
82+
83+ ( tabbables [ 0 ] ?? container ) . focus ( ) ;
3484 }
3585 } ) ;
3686
3787 const start = useCallback ( ( ) => {
3888 const document = ownerWindow ?. document ;
3989
40- if ( ! document || ! isMounted ( ) ) {
90+ if ( ! ownerWindow || ! document || ! isMounted ( ) ) {
4191 return ;
4292 }
4393
44- document . addEventListener ( 'focus' , handleFocus , { capture : true } ) ;
94+ ownerWindow . addEventListener ( 'focus' , handleFocus , { capture : true } ) ;
4595 document . addEventListener ( 'keydown' , handleKeydown ) ;
4696
47- listenersRef . current . add ( handleFocus ) ;
48- listenersRef . current . add ( handleKeydown ) ;
49-
50- function handleKeydown ( event : KeyboardEvent ) {
51- const tabbables = getTabbableElements ( container ) ;
52- const currentActiveElement = activeElement ( ownerWindow ?. document ) ;
53-
54- const isTabbingBackwards =
55- currentActiveElement === tabbables [ 0 ] && event . shiftKey ;
56-
57- const isTabbingForward =
58- currentActiveElement === tabbables [ tabbables . length - 1 ] ;
59-
60- if ( ! isTabbingBackwards && ! isTabbingForward ) {
61- return ;
62- }
63-
64- /**
65- * We want focus to move from the focus trapped container out of the document like
66- * it would if you tabbed from the end or start of the page.
67- *
68- * Generally this Just Works for tabbing forward out of the modal, as modals are often
69- * the last element in the document. In cases where it isn't or if you tabbed backwards
70- * we need to allow focus to move out of the document.
71- *
72- * This is done by way of a little "trick". `tab` events happen before focus moves so
73- * you can shift the focus to a new element and the tab will then move focus forward or backwards
74- * depending on the direction. We take advantage of this by moving focus to the first or last tabbable element
75- * in the document.
76- */
77- const bodyTabbables = getTabbableElements (
78- ownerWindow ?. document ?? document ! ,
79- ) ;
80-
81- if ( isTabbingBackwards ) {
82- if ( bodyTabbables [ 0 ] !== currentActiveElement ) {
83- bodyTabbables [ 0 ] ?. focus ( ) ;
84- }
85- } else if ( isTabbingForward ) {
86- const lastTabbable = bodyTabbables [ bodyTabbables . length - 1 ] ;
87-
88- if ( lastTabbable !== currentActiveElement ) {
89- lastTabbable ?. focus ( ) ;
90- }
91- }
92- }
97+ listenersRef . current . add ( ( ) =>
98+ ownerWindow . removeEventListener ( 'focus' , handleFocus , { capture : true } ) ,
99+ ) ;
100+ listenersRef . current . add ( ( ) =>
101+ document . removeEventListener ( 'keydown' , handleKeydown ) ,
102+ ) ;
93103
94104 function handleFocus ( event : FocusEvent ) {
105+ console . log ( 'handleFocus' , event . target ) ;
95106 // the timeout is necessary b/c this will run before the new modal is mounted
96107 // and so steals focus from it
97108 setTimeout ( ( ) => handleEnforceFocus ( event ) ) ;
0 commit comments