@@ -47,6 +47,7 @@ const Dropdown = (props: DropdownProps) => {
4747 const dropdownContentRef = useRef < HTMLDivElement > (
4848 document . createElement ( 'div' )
4949 ) ;
50+ const searchInputRef = useRef < HTMLInputElement > ( null ) ;
5051
5152 const ctx = window . dash_component_api . useDashContext ( ) ;
5253 const loading = ctx . useLoading ( ) ;
@@ -234,22 +235,33 @@ const Dropdown = (props: DropdownProps) => {
234235 }
235236 } , [ filteredOptions , isOpen ] ) ;
236237
237- // Focus (and scroll) the first selected item when dropdown opens
238+ // Focus first selected item or search input when dropdown opens
238239 useEffect ( ( ) => {
239- if ( ! isOpen || multi || search_value ) {
240+ if ( ! isOpen || search_value ) {
240241 return ;
241242 }
242243
243244 // waiting for the DOM to be ready after the dropdown renders
244245 requestAnimationFrame ( ( ) => {
245- const selectedValue = sanitizedValues [ 0 ] ;
246-
247- const selectedElement = dropdownContentRef . current . querySelector (
248- `.dash-options-list-option-checkbox[value="${ selectedValue } "]`
249- ) ;
246+ // Try to focus the first selected item (for single-select)
247+ if ( ! multi ) {
248+ const selectedValue = sanitizedValues [ 0 ] ;
249+ if ( selectedValue ) {
250+ const selectedElement =
251+ dropdownContentRef . current . querySelector (
252+ `.dash-options-list-option-checkbox[value="${ selectedValue } "]`
253+ ) ;
254+
255+ if ( selectedElement instanceof HTMLElement ) {
256+ selectedElement . focus ( ) ;
257+ return ;
258+ }
259+ }
260+ }
250261
251- if ( selectedElement instanceof HTMLElement ) {
252- selectedElement ?. focus ( ) ;
262+ // Fallback: focus search input if available and no selected item was focused
263+ if ( searchable && searchInputRef . current ) {
264+ searchInputRef . current . focus ( ) ;
253265 }
254266 } ) ;
255267 } , [ isOpen , multi , displayOptions , sanitizedValues ] ) ;
@@ -335,7 +347,7 @@ const Dropdown = (props: DropdownProps) => {
335347 } else {
336348 focusableElements [ nextIndex ] . scrollIntoView ( {
337349 behavior : 'auto' ,
338- block : 'center ' ,
350+ block : 'nearest ' ,
339351 } ) ;
340352 }
341353 }
@@ -354,8 +366,9 @@ const Dropdown = (props: DropdownProps) => {
354366 ) ;
355367
356368 const accessibleId = id ?? uuid ( ) ;
369+ const positioningContainerRef = useRef < HTMLDivElement > ( null ) ;
357370
358- return (
371+ const popover = (
359372 < Popover . Root open = { isOpen } onOpenChange = { handleOpenChange } >
360373 < Popover . Trigger asChild >
361374 < button
@@ -365,6 +378,7 @@ const Dropdown = (props: DropdownProps) => {
365378 type = "button"
366379 onKeyDown = { e => {
367380 if ( e . key === 'ArrowDown' ) {
381+ e . preventDefault ( ) ;
368382 setIsOpen ( true ) ;
369383 }
370384 } }
@@ -426,7 +440,7 @@ const Dropdown = (props: DropdownProps) => {
426440 // container is required otherwise popover will be rendered
427441 // at document root, which may be outside of the Dash app (i.e.
428442 // an embedded app)
429- container = { dropdownContainerRef . current ?. parentElement }
443+ container = { positioningContainerRef . current }
430444 >
431445 < Popover . Content
432446 ref = { dropdownContentRef }
@@ -451,7 +465,7 @@ const Dropdown = (props: DropdownProps) => {
451465 value = { search_value || '' }
452466 autoComplete = "off"
453467 onChange = { e => onInputChange ( e . target . value ) }
454- autoFocus
468+ ref = { searchInputRef }
455469 />
456470 { search_value && (
457471 < button
@@ -512,6 +526,12 @@ const Dropdown = (props: DropdownProps) => {
512526 </ Popover . Portal >
513527 </ Popover . Root >
514528 ) ;
529+
530+ return (
531+ < div ref = { positioningContainerRef } className = "dash-dropdown-wrapper" >
532+ { popover }
533+ </ div >
534+ ) ;
515535} ;
516536
517537export default Dropdown ;
0 commit comments