@@ -27,7 +27,15 @@ import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
27
27
import Magnifier from '@spectrum-icons/ui/Magnifier' ;
28
28
import { mergeProps , useId } from '@react-aria/utils' ;
29
29
import { 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' ;
31
39
import searchAutocompleteStyles from './searchautocomplete.css' ;
32
40
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css' ;
33
41
import { setInteractionModality , useHover } from '@react-aria/interactions' ;
@@ -45,7 +53,7 @@ import {useOverlayTrigger} from '@react-aria/overlays';
45
53
import { useProviderProps } from '@react-spectrum/provider' ;
46
54
import { useSearchAutocomplete } from '@react-aria/autocomplete' ;
47
55
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 > ) {
49
57
props = useProviderProps ( props ) ;
50
58
51
59
let {
@@ -71,7 +79,7 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
71
79
defaultSelectedKey : undefined
72
80
} ) ;
73
81
74
- let buttonRef = useRef < HTMLElement > ( ) ;
82
+ let buttonRef = useRef < HTMLDivElement > ( null ) ;
75
83
let domRef = useFocusableRef ( ref , buttonRef ) ;
76
84
let { triggerProps, overlayProps} = useOverlayTrigger ( { type : 'listbox' } , state , buttonRef ) ;
77
85
@@ -82,7 +90,7 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
82
90
83
91
// Focus the button and show focus ring when clicking on the label
84
92
labelProps . onClick = ( ) => {
85
- if ( ! props . isDisabled ) {
93
+ if ( ! props . isDisabled && buttonRef . current ) {
86
94
buttonRef . current . focus ( ) ;
87
95
setInteractionModality ( 'keyboard' ) ;
88
96
}
@@ -119,10 +127,13 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
119
127
</ Tray >
120
128
</ >
121
129
) ;
122
- } ) ;
130
+ }
131
+
132
+ export let MobileSearchAutocomplete = React . forwardRef ( _MobileSearchAutocomplete ) as < T > ( props : SpectrumSearchAutocompleteProps < T > & { ref ?: FocusableRef < HTMLElement > } ) => ReactElement ;
133
+
123
134
124
135
interface SearchAutocompleteButtonProps extends AriaButtonProps {
125
- icon ?: ReactElement ,
136
+ icon ?: ReactElement | null ,
126
137
isQuiet ?: boolean ,
127
138
isDisabled ?: boolean ,
128
139
isReadOnly ?: boolean ,
@@ -135,7 +146,9 @@ interface SearchAutocompleteButtonProps extends AriaButtonProps {
135
146
className ?: string
136
147
}
137
148
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 ) {
139
152
let searchIcon = (
140
153
< Magnifier data-testid = "searchicon" />
141
154
) ;
@@ -173,8 +186,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
173
186
let clearButton = (
174
187
< ClearButton
175
188
onPress = { ( e ) => {
176
- clearInput ( ) ;
177
- props . onPress ( e ) ;
189
+ clearInput ?. ( ) ;
190
+ props ? .onPress ?. ( e ) ;
178
191
} }
179
192
preventFocus
180
193
aria-label = { stringFormatter . format ( 'clear' ) }
@@ -188,7 +201,6 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
188
201
isDisabled = { isDisabled } />
189
202
) ;
190
203
191
-
192
204
let validation = React . cloneElement ( validationIcon , {
193
205
UNSAFE_className : classNames (
194
206
textfieldStyles ,
@@ -217,7 +229,7 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
217
229
< div
218
230
{ ...mergeProps ( hoverProps , focusProps , buttonProps ) }
219
231
aria-haspopup = "dialog"
220
- ref = { ref as RefObject < HTMLDivElement > }
232
+ ref = { ref }
221
233
style = { { ...style , outline : 'none' } }
222
234
className = {
223
235
classNames (
@@ -303,14 +315,14 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
303
315
) ;
304
316
} ) ;
305
317
306
- interface SearchAutocompleteTrayProps extends SpectrumSearchAutocompleteProps < unknown > {
307
- state : ComboBoxState < unknown > ,
318
+ interface SearchAutocompleteTrayProps < T > extends SpectrumSearchAutocompleteProps < T > {
319
+ state : ComboBoxState < T > ,
308
320
overlayProps : HTMLAttributes < HTMLElement > ,
309
321
loadingIndicator ?: ReactElement ,
310
322
onClose : ( ) => void
311
323
}
312
324
313
- function SearchAutocompleteTray ( props : SearchAutocompleteTrayProps ) {
325
+ function SearchAutocompleteTray < T > ( props : SearchAutocompleteTrayProps < T > ) {
314
326
let searchIcon = (
315
327
< Magnifier data-testid = "searchicon" />
316
328
) ;
@@ -329,15 +341,15 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
329
341
onSubmit
330
342
} = props ;
331
343
332
- let timeout = useRef ( null ) ;
344
+ let timeout = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
333
345
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 ) ;
337
349
let layout = useListBoxLayout ( state ) ;
338
350
let stringFormatter = useLocalizedStringFormatter ( intlMessages ) ;
339
351
340
- let { inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete (
352
+ let { inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete < T > (
341
353
{
342
354
...props ,
343
355
keyboardDelegate : layout ,
@@ -349,7 +361,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
349
361
) ;
350
362
351
363
React . useEffect ( ( ) => {
352
- focusSafely ( inputRef . current ) ;
364
+ if ( inputRef . current ) {
365
+ focusSafely ( inputRef . current ) ;
366
+ }
353
367
354
368
// When the tray unmounts, set state.isFocused (i.e. the tray input's focus tracker) to false.
355
369
// 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) {
421
435
return ;
422
436
}
423
437
424
- popoverRef . current . focus ( ) ;
438
+ if ( popoverRef . current ) {
439
+ popoverRef . current . focus ( ) ;
440
+ }
425
441
} , [ inputRef , popoverRef , isTouchDown ] ) ;
426
442
427
443
let inputValue = inputProps . value ;
@@ -444,8 +460,10 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
444
460
} else if ( loadingState !== 'filtering' ) {
445
461
// If loading is no longer happening, clear any timers and hide the loading circle
446
462
setShowLoading ( false ) ;
447
- clearTimeout ( timeout . current ) ;
448
- timeout . current = null ;
463
+ if ( timeout . current !== null ) {
464
+ clearTimeout ( timeout . current ) ;
465
+ timeout . current = null ;
466
+ }
449
467
}
450
468
451
469
lastInputValue . current = inputValue ;
@@ -454,11 +472,17 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
454
472
let onKeyDown = ( e ) => {
455
473
// Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
456
474
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
+ }
460
482
} else {
461
- inputProps . onKeyDown ( e ) ;
483
+ if ( inputProps . onKeyDown ) {
484
+ inputProps . onKeyDown ( e ) ;
485
+ }
462
486
}
463
487
} ;
464
488
@@ -491,9 +515,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
491
515
inputRef = { inputRef }
492
516
isDisabled = { isDisabled }
493
517
isLoading = { showLoading && loadingState === 'filtering' }
494
- loadingIndicator = { loadingState != null && loadingCircle }
518
+ loadingIndicator = { loadingState != null ? loadingCircle : undefined }
495
519
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 }
497
521
icon = { icon }
498
522
UNSAFE_className = {
499
523
classNames (
0 commit comments