@@ -5,7 +5,7 @@ import * as React from 'react';
55import { useUncontrolledProp } from 'uncontrollable' ;
66import usePrevious from '@restart/hooks/usePrevious' ;
77import useForceUpdate from '@restart/hooks/useForceUpdate' ;
8- import useGlobalListener from '@restart/hooks/useGlobalListener ' ;
8+ import useEventListener from '@restart/hooks/useEventListener ' ;
99import useEventCallback from '@restart/hooks/useEventCallback' ;
1010
1111import DropdownContext from './DropdownContext' ;
@@ -24,6 +24,7 @@ import SelectableContext from './SelectableContext';
2424import { SelectCallback } from './types' ;
2525import { dataAttr } from './DataKey' ;
2626import { Placement } from './usePopper' ;
27+ import useWindow from './useWindow' ;
2728
2829export type {
2930 DropdownMenuProps ,
@@ -147,6 +148,7 @@ function Dropdown({
147148 placement = 'bottom-start' ,
148149 children,
149150} : DropdownProps ) {
151+ const window = useWindow ( ) ;
150152 const [ show , onToggle ] = useUncontrolledProp (
151153 rawShow ,
152154 defaultShow ! ,
@@ -203,7 +205,9 @@ function Dropdown({
203205 ) ;
204206
205207 if ( menuElement && lastShow && ! show ) {
206- focusInDropdown . current = menuElement . contains ( document . activeElement ) ;
208+ focusInDropdown . current = menuElement . contains (
209+ menuElement . ownerDocument . activeElement ,
210+ ) ;
207211 }
208212
209213 const focusToggle = useEventCallback ( ( ) => {
@@ -256,77 +260,81 @@ function Dropdown({
256260 return items [ index ] ;
257261 } ;
258262
259- useGlobalListener ( 'keydown' , ( event : KeyboardEvent ) => {
260- const { key } = event ;
261- const target = event . target as HTMLElement ;
263+ useEventListener (
264+ useCallback ( ( ) => window ! . document , [ window ] ) ,
265+ 'keydown' ,
266+ ( event : KeyboardEvent ) => {
267+ const { key } = event ;
268+ const target = event . target as HTMLElement ;
262269
263- const fromMenu = menuRef . current ?. contains ( target ) ;
264- const fromToggle = toggleRef . current ?. contains ( target ) ;
270+ const fromMenu = menuRef . current ?. contains ( target ) ;
271+ const fromToggle = toggleRef . current ?. contains ( target ) ;
265272
266- // Second only to https://github.com/twbs/bootstrap/blob/8cfbf6933b8a0146ac3fbc369f19e520bd1ebdac/js/src/dropdown.js#L400
267- // in inscrutability
268- const isInput = / i n p u t | t e x t a r e a / i. test ( target . tagName ) ;
269- if ( isInput && ( key === ' ' || ( key !== 'Escape' && fromMenu ) ) ) {
270- return ;
271- }
272-
273- if ( ! fromMenu && ! fromToggle ) {
274- return ;
275- }
276-
277- if ( key === 'Tab' && ( ! menuRef . current || ! show ) ) {
278- return ;
279- }
273+ // Second only to https://github.com/twbs/bootstrap/blob/8cfbf6933b8a0146ac3fbc369f19e520bd1ebdac/js/src/dropdown.js#L400
274+ // in inscrutability
275+ const isInput = / i n p u t | t e x t a r e a / i. test ( target . tagName ) ;
276+ if ( isInput && ( key === ' ' || ( key !== 'Escape' && fromMenu ) ) ) {
277+ return ;
278+ }
280279
281- lastSourceEvent . current = event . type ;
282- const meta = { originalEvent : event , source : event . type } ;
283- switch ( key ) {
284- case 'ArrowUp' : {
285- const next = getNextFocusedChild ( target , - 1 ) ;
286- if ( next && next . focus ) next . focus ( ) ;
287- event . preventDefault ( ) ;
280+ if ( ! fromMenu && ! fromToggle ) {
281+ return ;
282+ }
288283
284+ if ( key === 'Tab' && ( ! menuRef . current || ! show ) ) {
289285 return ;
290286 }
291- case 'ArrowDown' :
292- event . preventDefault ( ) ;
293- if ( ! show ) {
294- onToggle ( true , meta ) ;
295- } else {
296- const next = getNextFocusedChild ( target , 1 ) ;
287+
288+ lastSourceEvent . current = event . type ;
289+ const meta = { originalEvent : event , source : event . type } ;
290+ switch ( key ) {
291+ case 'ArrowUp' : {
292+ const next = getNextFocusedChild ( target , - 1 ) ;
297293 if ( next && next . focus ) next . focus ( ) ;
298- }
299- return ;
300- case 'Tab' :
301- // on keydown the target is the element being tabbed FROM, we need that
302- // to know if this event is relevant to this dropdown (e.g. in this menu).
303- // On `keyup` the target is the element being tagged TO which we use to check
304- // if focus has left the menu
305- addEventListener (
306- document as any ,
307- 'keyup' ,
308- ( e ) => {
309- if (
310- ( e . key === 'Tab' && ! e . target ) ||
311- ! menuRef . current ?. contains ( e . target as HTMLElement )
312- ) {
313- onToggle ( false , meta ) ;
314- }
315- } ,
316- { once : true } ,
317- ) ;
318- break ;
319- case 'Escape' :
320- if ( key === 'Escape' ) {
321294 event . preventDefault ( ) ;
322- event . stopPropagation ( ) ;
323- }
324295
325- onToggle ( false , meta ) ;
326- break ;
327- default :
328- }
329- } ) ;
296+ return ;
297+ }
298+ case 'ArrowDown' :
299+ event . preventDefault ( ) ;
300+ if ( ! show ) {
301+ onToggle ( true , meta ) ;
302+ } else {
303+ const next = getNextFocusedChild ( target , 1 ) ;
304+ if ( next && next . focus ) next . focus ( ) ;
305+ }
306+ return ;
307+ case 'Tab' :
308+ // on keydown the target is the element being tabbed FROM, we need that
309+ // to know if this event is relevant to this dropdown (e.g. in this menu).
310+ // On `keyup` the target is the element being tagged TO which we use to check
311+ // if focus has left the menu
312+ addEventListener (
313+ target . ownerDocument as any ,
314+ 'keyup' ,
315+ ( e ) => {
316+ if (
317+ ( e . key === 'Tab' && ! e . target ) ||
318+ ! menuRef . current ?. contains ( e . target as HTMLElement )
319+ ) {
320+ onToggle ( false , meta ) ;
321+ }
322+ } ,
323+ { once : true } ,
324+ ) ;
325+ break ;
326+ case 'Escape' :
327+ if ( key === 'Escape' ) {
328+ event . preventDefault ( ) ;
329+ event . stopPropagation ( ) ;
330+ }
331+
332+ onToggle ( false , meta ) ;
333+ break ;
334+ default :
335+ }
336+ } ,
337+ ) ;
330338
331339 return (
332340 < SelectableContext . Provider value = { handleSelect } >
0 commit comments