@@ -56,3 +56,80 @@ it('closes and returns focus to trigger on Escape key', () => {
5656 trigger . remove ( ) ;
5757 }
5858} ) ;
59+
60+ it ( 'lets an overlay handle Tab before the window listener' , ( ) => {
61+ const trigger = document . createElement ( 'button' ) ;
62+ const overlay = document . createElement ( 'div' ) ;
63+ const input = document . createElement ( 'input' ) ;
64+ overlay . tabIndex = - 1 ;
65+ overlay . appendChild ( input ) ;
66+ document . body . append ( trigger , overlay ) ;
67+ const onOpenChange = jest . fn ( ) ;
68+ input . addEventListener ( 'keydown' , ( event ) => event . stopPropagation ( ) ) ;
69+ const { unmount } = renderHook ( ( ) =>
70+ useAccessibility ( {
71+ open : true ,
72+ triggerRef : { current : trigger } ,
73+ overlayRef : { current : overlay } ,
74+ onOpenChange,
75+ } ) ,
76+ ) ;
77+ try {
78+ input . focus ( ) ;
79+ const event = new KeyboardEvent ( 'keydown' , {
80+ key : 'Tab' ,
81+ keyCode : 9 ,
82+ bubbles : true ,
83+ cancelable : true ,
84+ } ) ;
85+ act ( ( ) => {
86+ input . dispatchEvent ( event ) ;
87+ } ) ;
88+ expect ( document . activeElement ) . toBe ( input ) ;
89+ expect ( event . defaultPrevented ) . toBe ( false ) ;
90+ expect ( onOpenChange ) . not . toHaveBeenCalled ( ) ;
91+ } finally {
92+ unmount ( ) ;
93+ trigger . remove ( ) ;
94+ overlay . remove ( ) ;
95+ }
96+ } ) ;
97+
98+ it ( 'restores focus before an earlier window bubble listener unmounts the hook' , ( ) => {
99+ const trigger = document . createElement ( 'button' ) ;
100+ const item = document . createElement ( 'button' ) ;
101+ document . body . append ( trigger , item ) ;
102+ item . focus ( ) ;
103+ const onOpenChange = jest . fn ( ) ;
104+ let teardown = ( ) => { } ;
105+ const portalListener = ( ) => teardown ( ) ;
106+ window . addEventListener ( 'keydown' , portalListener ) ;
107+ const { unmount } = renderHook ( ( ) =>
108+ useAccessibility ( {
109+ open : true ,
110+ triggerRef : { current : trigger } ,
111+ onOpenChange,
112+ } ) ,
113+ ) ;
114+ teardown = unmount ;
115+ try {
116+ act ( ( ) => {
117+ item . dispatchEvent (
118+ new KeyboardEvent ( 'keydown' , {
119+ key : 'Escape' ,
120+ keyCode : 27 ,
121+ bubbles : true ,
122+ cancelable : true ,
123+ } ) ,
124+ ) ;
125+ } ) ;
126+ expect ( onOpenChange ) . toHaveBeenCalledTimes ( 1 ) ;
127+ expect ( onOpenChange ) . toHaveBeenCalledWith ( false ) ;
128+ expect ( document . activeElement ) . toBe ( trigger ) ;
129+ } finally {
130+ window . removeEventListener ( 'keydown' , portalListener ) ;
131+ unmount ( ) ;
132+ trigger . remove ( ) ;
133+ item . remove ( ) ;
134+ }
135+ } ) ;
0 commit comments