@@ -27,7 +27,15 @@ import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
2727import Magnifier from '@spectrum-icons/ui/Magnifier' ;
2828import { mergeProps , useId } from '@react-aria/utils' ;
2929import { ProgressCircle } from '@react-spectrum/progress' ;
30- import React , { HTMLAttributes , ReactElement , ReactNode , RefObject , useCallback , useEffect , useRef , useState } from 'react' ;
30+ import React , {
31+ HTMLAttributes ,
32+ ReactElement ,
33+ ReactNode ,
34+ useCallback ,
35+ useEffect ,
36+ useRef ,
37+ useState
38+ } from 'react' ;
3139import searchAutocompleteStyles from './searchautocomplete.css' ;
3240import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css' ;
3341import { setInteractionModality , useHover } from '@react-aria/interactions' ;
@@ -45,7 +53,7 @@ import {useOverlayTrigger} from '@react-aria/overlays';
4553import { useProviderProps } from '@react-spectrum/provider' ;
4654import { useSearchAutocomplete } from '@react-aria/autocomplete' ;
4755
48- export const MobileSearchAutocomplete = React . forwardRef ( function MobileSearchAutocomplete < T extends object > ( props : SpectrumSearchAutocompleteProps < T > , ref : FocusableRef < HTMLElement > ) {
56+ function _MobileSearchAutocomplete < T extends object > ( props : SpectrumSearchAutocompleteProps < T > , ref : FocusableRef < HTMLElement > ) {
4957 props = useProviderProps ( props ) ;
5058
5159 let {
@@ -71,7 +79,7 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
7179 defaultSelectedKey : undefined
7280 } ) ;
7381
74- let buttonRef = useRef < HTMLElement > ( ) ;
82+ let buttonRef = useRef < HTMLDivElement > ( null ) ;
7583 let domRef = useFocusableRef ( ref , buttonRef ) ;
7684 let { triggerProps, overlayProps} = useOverlayTrigger ( { type : 'listbox' } , state , buttonRef ) ;
7785
@@ -82,7 +90,7 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
8290
8391 // Focus the button and show focus ring when clicking on the label
8492 labelProps . onClick = ( ) => {
85- if ( ! props . isDisabled ) {
93+ if ( ! props . isDisabled && buttonRef . current ) {
8694 buttonRef . current . focus ( ) ;
8795 setInteractionModality ( 'keyboard' ) ;
8896 }
@@ -119,10 +127,13 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
119127 </ Tray >
120128 </ >
121129 ) ;
122- } ) ;
130+ }
131+
132+ export let MobileSearchAutocomplete = React . forwardRef ( _MobileSearchAutocomplete ) as < T > ( props : SpectrumSearchAutocompleteProps < T > & { ref ?: FocusableRef < HTMLElement > } ) => ReactElement ;
133+
123134
124135interface SearchAutocompleteButtonProps extends AriaButtonProps {
125- icon ?: ReactElement ,
136+ icon ?: ReactElement | null ,
126137 isQuiet ?: boolean ,
127138 isDisabled ?: boolean ,
128139 isReadOnly ?: boolean ,
@@ -135,7 +146,9 @@ interface SearchAutocompleteButtonProps extends AriaButtonProps {
135146 className ?: string
136147}
137148
138- const SearchAutocompleteButton = React . forwardRef ( function SearchAutocompleteButton ( props : SearchAutocompleteButtonProps , ref : RefObject < HTMLElement > ) {
149+ // any type is because we don't want to call useObjectRef because this is an internal component and we know
150+ // we are always passing an object ref
151+ const SearchAutocompleteButton = React . forwardRef ( function SearchAutocompleteButton ( props : SearchAutocompleteButtonProps , ref : any ) {
139152 let searchIcon = (
140153 < Magnifier data-testid = "searchicon" />
141154 ) ;
@@ -173,8 +186,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
173186 let clearButton = (
174187 < ClearButton
175188 onPress = { ( e ) => {
176- clearInput ( ) ;
177- props . onPress ( e ) ;
189+ clearInput ?. ( ) ;
190+ props ? .onPress ?. ( e ) ;
178191 } }
179192 preventFocus
180193 aria-label = { stringFormatter . format ( 'clear' ) }
@@ -188,7 +201,6 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
188201 isDisabled = { isDisabled } />
189202 ) ;
190203
191-
192204 let validation = React . cloneElement ( validationIcon , {
193205 UNSAFE_className : classNames (
194206 textfieldStyles ,
@@ -217,7 +229,7 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
217229 < div
218230 { ...mergeProps ( hoverProps , focusProps , buttonProps ) }
219231 aria-haspopup = "dialog"
220- ref = { ref as RefObject < HTMLDivElement > }
232+ ref = { ref }
221233 style = { { ...style , outline : 'none' } }
222234 className = {
223235 classNames (
@@ -303,14 +315,14 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
303315 ) ;
304316} ) ;
305317
306- interface SearchAutocompleteTrayProps extends SpectrumSearchAutocompleteProps < unknown > {
307- state : ComboBoxState < unknown > ,
318+ interface SearchAutocompleteTrayProps < T > extends SpectrumSearchAutocompleteProps < T > {
319+ state : ComboBoxState < T > ,
308320 overlayProps : HTMLAttributes < HTMLElement > ,
309321 loadingIndicator ?: ReactElement ,
310322 onClose : ( ) => void
311323}
312324
313- function SearchAutocompleteTray ( props : SearchAutocompleteTrayProps ) {
325+ function SearchAutocompleteTray < T > ( props : SearchAutocompleteTrayProps < T > ) {
314326 let searchIcon = (
315327 < Magnifier data-testid = "searchicon" />
316328 ) ;
@@ -329,15 +341,15 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
329341 onSubmit
330342 } = props ;
331343
332- let timeout = useRef ( null ) ;
344+ let timeout = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
333345 let [ showLoading , setShowLoading ] = useState ( false ) ;
334- let inputRef = useRef < HTMLInputElement > ( ) ;
335- let popoverRef = useRef < HTMLDivElement > ( ) ;
336- let listBoxRef = useRef < HTMLDivElement > ( ) ;
346+ let inputRef = useRef < HTMLInputElement > ( null ) ;
347+ let popoverRef = useRef < HTMLDivElement > ( null ) ;
348+ let listBoxRef = useRef < HTMLDivElement > ( null ) ;
337349 let layout = useListBoxLayout ( state ) ;
338350 let stringFormatter = useLocalizedStringFormatter ( intlMessages ) ;
339351
340- let { inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete (
352+ let { inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete < T > (
341353 {
342354 ...props ,
343355 keyboardDelegate : layout ,
@@ -349,7 +361,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
349361 ) ;
350362
351363 React . useEffect ( ( ) => {
352- focusSafely ( inputRef . current ) ;
364+ if ( inputRef . current ) {
365+ focusSafely ( inputRef . current ) ;
366+ }
353367
354368 // When the tray unmounts, set state.isFocused (i.e. the tray input's focus tracker) to false.
355369 // This is to prevent state.isFocused from being set to true when the tray closes via tapping on the underlay
@@ -421,7 +435,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
421435 return ;
422436 }
423437
424- popoverRef . current . focus ( ) ;
438+ if ( popoverRef . current ) {
439+ popoverRef . current . focus ( ) ;
440+ }
425441 } , [ inputRef , popoverRef , isTouchDown ] ) ;
426442
427443 let inputValue = inputProps . value ;
@@ -444,8 +460,10 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
444460 } else if ( loadingState !== 'filtering' ) {
445461 // If loading is no longer happening, clear any timers and hide the loading circle
446462 setShowLoading ( false ) ;
447- clearTimeout ( timeout . current ) ;
448- timeout . current = null ;
463+ if ( timeout . current !== null ) {
464+ clearTimeout ( timeout . current ) ;
465+ timeout . current = null ;
466+ }
449467 }
450468
451469 lastInputValue . current = inputValue ;
@@ -454,11 +472,17 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
454472 let onKeyDown = ( e ) => {
455473 // Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
456474 if ( e . key === 'Enter' && state . selectionManager . focusedKey == null ) {
457- popoverRef . current . focus ( ) ;
458- onClose ( ) ;
459- onSubmit ( inputValue . toString ( ) , null ) ;
475+ popoverRef . current ?. focus ( ) ;
476+ if ( onClose ) {
477+ onClose ( ) ;
478+ }
479+ if ( onSubmit ) {
480+ onSubmit ( inputValue == null ? null : inputValue . toString ( ) , null ) ;
481+ }
460482 } else {
461- inputProps . onKeyDown ( e ) ;
483+ if ( inputProps . onKeyDown ) {
484+ inputProps . onKeyDown ( e ) ;
485+ }
462486 }
463487 } ;
464488
@@ -491,9 +515,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
491515 inputRef = { inputRef }
492516 isDisabled = { isDisabled }
493517 isLoading = { showLoading && loadingState === 'filtering' }
494- loadingIndicator = { loadingState != null && loadingCircle }
518+ loadingIndicator = { loadingState != null ? loadingCircle : undefined }
495519 validationState = { validationState }
496- wrapperChildren = { ( state . inputValue !== '' || loadingState === 'filtering' || validationState != null ) && ! props . isReadOnly && clearButton }
520+ wrapperChildren = { ( ( state . inputValue !== '' || loadingState === 'filtering' || validationState != null ) && ! props . isReadOnly ) ? clearButton : undefined }
497521 icon = { icon }
498522 UNSAFE_className = {
499523 classNames (
0 commit comments