@@ -17,6 +17,7 @@ import {
17
17
} from 'sentry/components/compactSelect/utils' ;
18
18
import { GrowingInput } from 'sentry/components/growingInput' ;
19
19
import Input from 'sentry/components/input' ;
20
+ import InteractionStateLayer from 'sentry/components/interactionStateLayer' ;
20
21
import LoadingIndicator from 'sentry/components/loadingIndicator' ;
21
22
import { Overlay , PositionWrapper } from 'sentry/components/overlay' ;
22
23
import { t } from 'sentry/locale' ;
@@ -34,7 +35,10 @@ import type {
34
35
} from './types' ;
35
36
36
37
interface ComboBoxProps < Value extends string >
37
- extends ComboBoxStateOptions < ComboBoxOptionOrSection < Value > > {
38
+ extends Omit <
39
+ ComboBoxStateOptions < ComboBoxOptionOrSection < Value > > ,
40
+ 'allowsCustomValue'
41
+ > {
38
42
'aria-label' : string ;
39
43
className ?: string ;
40
44
disabled ?: boolean ;
@@ -59,6 +63,7 @@ function ComboBox<Value extends string>({
59
63
sizeLimitMessage,
60
64
menuTrigger = 'focus' ,
61
65
growingInput = false ,
66
+ onOpenChange,
62
67
menuWidth,
63
68
...props
64
69
} : ComboBoxProps < Value > ) {
@@ -70,10 +75,25 @@ function ComboBox<Value extends string>({
70
75
const state = useComboBoxState ( {
71
76
// Mapping our disabled prop to react-aria's isDisabled
72
77
isDisabled : disabled ,
78
+ onOpenChange : ( isOpen , ...otherArgs ) => {
79
+ onOpenChange ?.( isOpen , ...otherArgs ) ;
80
+ if ( isOpen ) {
81
+ // Ensure the selected element is being focused
82
+ state . selectionManager . setFocusedKey ( state . selectedKey ) ;
83
+ }
84
+ } ,
73
85
...props ,
74
86
} ) ;
87
+
75
88
const { inputProps, listBoxProps} = useComboBox (
76
- { listBoxRef, inputRef, popoverRef, isDisabled : disabled , ...props } ,
89
+ {
90
+ listBoxRef,
91
+ inputRef,
92
+ popoverRef,
93
+ shouldFocusWrap : true ,
94
+ isDisabled : disabled ,
95
+ ...props ,
96
+ } ,
77
97
state
78
98
) ;
79
99
@@ -89,6 +109,14 @@ function ComboBox<Value extends string>({
89
109
return ( ) => { } ;
90
110
} , [ menuWidth , state . isOpen ] ) ;
91
111
112
+ useEffect ( ( ) => {
113
+ const popoverElement = popoverRef . current ;
114
+ // Reset scroll state on opening the popover
115
+ if ( popoverElement ) {
116
+ popoverElement . scrollTop = 0 ;
117
+ }
118
+ } , [ state . isOpen ] ) ;
119
+
92
120
const selectContext = useContext ( SelectContext ) ;
93
121
94
122
const { overlayProps, triggerProps} = useOverlay ( {
@@ -97,22 +125,41 @@ function ComboBox<Value extends string>({
97
125
position : 'bottom-start' ,
98
126
offset : [ 0 , 8 ] ,
99
127
isDismissable : true ,
100
- isKeyboardDismissDisabled : true ,
101
128
onInteractOutside : ( ) => {
102
129
state . close ( ) ;
103
130
inputRef . current ?. blur ( ) ;
104
131
} ,
105
132
shouldCloseOnBlur : true ,
106
133
} ) ;
107
134
108
- // The menu opens after selecting an item but the input stais focused
135
+ // The menu opens after selecting an item but the input stays focused
109
136
// This ensures the user can open the menu again by clicking on the input
110
137
const handleInputClick = useCallback ( ( ) => {
111
138
if ( ! state . isOpen && menuTrigger === 'focus' ) {
112
139
state . open ( ) ;
113
140
}
114
141
} , [ state , menuTrigger ] ) ;
115
142
143
+ const handleInputMouseUp = useCallback ( ( event : React . MouseEvent < HTMLInputElement > ) => {
144
+ // Prevents the input from being selected when clicking on the trigger
145
+ event . preventDefault ( ) ;
146
+ } , [ ] ) ;
147
+
148
+ const handleInputFocus = useCallback (
149
+ ( event : React . FocusEvent < HTMLInputElement > ) => {
150
+ const onFocusProp = inputProps . onFocus ;
151
+ onFocusProp ?.( event ) ;
152
+ if ( menuTrigger === 'focus' ) {
153
+ state . open ( ) ;
154
+ }
155
+ // Need to setTimeout otherwise Chrome might reset the selection on padding click
156
+ setTimeout ( ( ) => {
157
+ event . target . select ( ) ;
158
+ } , 0 ) ;
159
+ } ,
160
+ [ inputProps . onFocus , menuTrigger , state ]
161
+ ) ;
162
+
116
163
const InputComponent = growingInput ? StyledGrowingInput : StyledInput ;
117
164
118
165
return (
@@ -123,10 +170,13 @@ function ComboBox<Value extends string>({
123
170
} }
124
171
>
125
172
< ControlWrapper className = { className } >
173
+ { ! state . isFocused && < InteractionStateLayer /> }
126
174
< InputComponent
127
175
{ ...inputProps }
128
176
onClick = { handleInputClick }
129
177
placeholder = { placeholder }
178
+ onMouseUp = { handleInputMouseUp }
179
+ onFocus = { handleInputFocus }
130
180
ref = { mergeRefs ( [ inputRef , triggerProps . ref ] ) }
131
181
size = { size }
132
182
/>
@@ -307,15 +357,22 @@ const ControlWrapper = styled('div')`
307
357
width: max-content;
308
358
min-width: 150px;
309
359
max-width: 100%;
360
+ cursor: pointer;
310
361
` ;
311
362
312
363
const StyledInput = styled ( Input ) `
313
364
max-width: inherit;
314
365
min-width: inherit;
366
+ &:not(:focus) {
367
+ pointer-events: none;
368
+ }
315
369
` ;
316
370
const StyledGrowingInput = styled ( GrowingInput ) `
317
371
max-width: inherit;
318
372
min-width: inherit;
373
+ &:not(:focus) {
374
+ cursor: pointer;
375
+ }
319
376
` ;
320
377
321
378
const StyledPositionWrapper = styled ( PositionWrapper , {
0 commit comments