@@ -57,7 +57,7 @@ export interface SelectProps<T extends React.Key = string>
5757
5858// Default search input built on top of CommandInput. Supports cmdk props at runtime.
5959const DefaultSearchInput = forwardRef < HTMLInputElement , React . ComponentPropsWithoutRef < typeof CommandInput > > (
60- ( props , _ref ) => < CommandInput { ...props } /> ,
60+ ( props , ref ) => < CommandInput { ...props } /> ,
6161) ;
6262DefaultSearchInput . displayName = 'SelectSearchInput' ;
6363
@@ -84,17 +84,34 @@ export function Select<T extends React.Key = string>({
8484 const triggerRef = useRef < HTMLButtonElement > ( null ) ;
8585 const popoverRef = useRef < HTMLDivElement > ( null ) ;
8686 const selectedItemRef = useRef < HTMLElement > ( null ) ;
87+ const searchInputRef = useRef < HTMLInputElement > ( null ) ;
8788 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
8889 // No need for JavaScript width measurement - Radix provides --radix-popover-trigger-width CSS variable
8990
9091 // When opening, ensure the currently selected option is the active item for keyboard nav
92+ // and focus the search input if searchable
9193 useEffect ( ( ) => {
92- if ( ! popoverState . isOpen ) return ;
94+ if ( ! popoverState . isOpen ) {
95+ // Clear search query when closing
96+ setSearchQuery ( '' ) ;
97+ return ;
98+ }
9399 requestAnimationFrame ( ( ) => {
100+ if ( searchable ) {
101+ // Query for the input element since CommandInput uses asChild and ref forwarding is complex
102+ const inputElement = popoverRef . current ?. querySelector < HTMLInputElement > ( 'input[type="text"]' ) ;
103+ if ( inputElement ) {
104+ inputElement . focus ( ) ;
105+ const selectionEnd = inputElement . value . length ;
106+ if ( selectionEnd > 0 ) {
107+ inputElement . setSelectionRange ( selectionEnd , selectionEnd ) ;
108+ }
109+ }
110+ }
94111 const selectedEl = selectedItemRef . current as HTMLElement | null ;
95112 if ( selectedEl ) selectedEl . scrollIntoView ( { block : 'center' } ) ;
96113 } ) ;
97- } , [ popoverState . isOpen ] ) ;
114+ } , [ popoverState . isOpen , searchable ] ) ;
98115
99116 const selectedOption = options . find ( ( o ) => o . value === value ) ;
100117
@@ -116,6 +133,34 @@ export function Select<T extends React.Key = string>({
116133 const ChevronIcon = components ?. ChevronIcon || DefaultChevronIcon ;
117134 const SearchInput = components ?. SearchInput || DefaultSearchInput ;
118135
136+ // Handle keydown on trigger to open popover and start typing
137+ const handleTriggerKeyDown = ( e : React . KeyboardEvent < HTMLButtonElement > ) => {
138+ // Allow normal keyboard navigation (Enter, Space, Arrow keys, etc.)
139+ if (
140+ e . key === 'Enter' ||
141+ e . key === ' ' ||
142+ e . key === 'ArrowDown' ||
143+ e . key === 'ArrowUp' ||
144+ e . key === 'Escape' ||
145+ e . key === 'Tab'
146+ ) {
147+ return ;
148+ }
149+
150+ // If it's a printable character and searchable is enabled, open the popover and start typing
151+ if ( searchable && e . key . length === 1 && ! e . ctrlKey && ! e . metaKey && ! e . altKey ) {
152+ e . preventDefault ( ) ;
153+ if ( ! popoverState . isOpen ) {
154+ popoverState . open ( ) ;
155+ }
156+ // Set the initial search query
157+ setSearchQuery ( e . key ) ;
158+ }
159+
160+ // Call the original onKeyDown if provided
161+ buttonProps . onKeyDown ?.( e ) ;
162+ } ;
163+
119164 return (
120165 < Popover open = { popoverState . isOpen } onOpenChange = { popoverState . setOpen } >
121166 < PopoverTrigger asChild >
@@ -131,6 +176,7 @@ export function Select<T extends React.Key = string>({
131176 aria-haspopup = "listbox"
132177 aria-expanded = { popoverState . isOpen }
133178 aria-controls = { listboxId }
179+ onKeyDown = { handleTriggerKeyDown }
134180 { ...buttonProps }
135181 >
136182 { value != null && value !== '' ? ( selectedOption ?. label ?? String ( value ) ) : placeholder }
@@ -159,6 +205,7 @@ export function Select<T extends React.Key = string>({
159205 { searchable && (
160206 < div className = "px-1.5 pb-1.5 pt-1.5" >
161207 < SearchInput
208+ ref = { searchInputRef }
162209 placeholder = "Search..."
163210 value = { searchQuery }
164211 onValueChange = { ( v : string ) => {
0 commit comments