@@ -54,6 +54,7 @@ export const Dropdown: FC<DropdownProps> = React.memo(
5454 closeOnDropdownClick = true ,
5555 closeOnReferenceClick = true ,
5656 closeOnOutsideClick = true ,
57+ shouldCloseOnTab = false ,
5758 disabled,
5859 dropdownClassNames,
5960 dropdownStyle,
@@ -124,6 +125,14 @@ export const Dropdown: FC<DropdownProps> = React.memo(
124125 return focusableElements ?. [ 0 ] ;
125126 } ;
126127
128+ const getFocusableItems = ( ) : HTMLElement [ ] => {
129+ if ( ! refs . floating . current ) return [ ] ;
130+
131+ return Array . from (
132+ refs . floating . current . querySelectorAll < HTMLElement > ( SELECTORS )
133+ ) . filter ( ( el ) => focusable ( el ) ) ;
134+ } ;
135+
127136 const focusFirstElement = ( ) : void => {
128137 const elementToFocus : HTMLElement = firstFocusableElement ?.( ) ;
129138 clearInterval ( intervalRef ?. current ) ;
@@ -285,23 +294,65 @@ export const Dropdown: FC<DropdownProps> = React.memo(
285294 event ?. preventDefault ( ) ;
286295 toggle ( false ) ( event ) ;
287296 }
297+ if ( event ?. key === eventKeys . ESCAPE ) {
298+ toggle ( false ) ( event ) ;
299+ }
300+ if ( event ?. key === eventKeys . TAB && mergedVisible && shouldCloseOnTab ) {
301+ toggle ( false ) ( event ) ;
302+ }
288303 if (
289- event ?. key === eventKeys . ESCAPE ||
290- ( event ?. key === eventKeys . TAB && mergedVisible ) ||
291- ( event ?. key === eventKeys . TAB &&
292- event . shiftKey &&
293- ! ( event . target as HTMLElement ) . matches ( ':focus-within' ) )
304+ event ?. key === eventKeys . TAB &&
305+ event . shiftKey &&
306+ ! ( event . target as HTMLElement ) . matches ( ':focus-within' )
294307 ) {
295308 toggle ( false ) ( event ) ;
296309 }
297310 } ;
298311
299312 const handleFloatingKeyDown = ( event : React . KeyboardEvent ) : void => {
300313 if (
301- event ?. key === eventKeys . ESCAPE ||
302- ( event ? .key === eventKeys . TAB && mergedVisible )
314+ ! event . defaultPrevented &&
315+ ( event . key === eventKeys . ARROWDOWN || event . key === eventKeys . ARROWUP )
303316 ) {
317+ const items = getFocusableItems ( ) ;
318+ const currentIndex = items . indexOf (
319+ document . activeElement as HTMLElement
320+ ) ;
321+
322+ if ( event . key === eventKeys . ARROWDOWN ) {
323+ event . preventDefault ( ) ;
324+ const next = items [ currentIndex + 1 ] || items [ 0 ] ;
325+ next ?. focus ( ) ;
326+ return ;
327+ }
328+
329+ if ( event . key === eventKeys . ARROWUP ) {
330+ event . preventDefault ( ) ;
331+ const prev = items [ currentIndex - 1 ] || items [ items . length - 1 ] ;
332+ prev ?. focus ( ) ;
333+ return ;
334+ }
335+ }
336+
337+ if ( event . key === eventKeys . ESCAPE ) {
304338 toggle ( false ) ( event ) ;
339+ return ;
340+ }
341+
342+ if ( event ?. key === eventKeys . TAB && mergedVisible && ! event . shiftKey ) {
343+ if ( shouldCloseOnTab ) {
344+ toggle ( false ) ( event ) ;
345+ } else {
346+ timeout && clearTimeout ( timeout ) ;
347+ timeout = setTimeout ( ( ) => {
348+ if (
349+ refs . floating . current &&
350+ ! refs . floating . current . contains ( document . activeElement )
351+ ) {
352+ toggle ( false ) ( event ) ;
353+ }
354+ } , NO_ANIMATION_DURATION ) ;
355+ }
305356 }
306357 if ( event ?. key === eventKeys . TAB && event . shiftKey && mergedVisible ) {
307358 timeout && clearTimeout ( timeout ) ;
0 commit comments