@@ -62,6 +62,9 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
62
62
/** The initial value for the index of the currently active selected item, in a multiple selection. */
63
63
defaultActiveSelectedIndex ?: number
64
64
65
+ /** Initial value for 'open' in uncontrolled mode */
66
+ defaultOpen ?: boolean
67
+
65
68
/** The initial value for the search query, if the dropdown is also a search. */
66
69
defaultSearchQuery ?: string
67
70
@@ -114,6 +117,13 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
114
117
/** A message to be displayed in the list when dropdown has no available items to show. */
115
118
noResultsMessage ?: ShorthandValue
116
119
120
+ /**
121
+ * Callback for change in dropdown open value.
122
+ * @param {SyntheticEvent } event - React's original SyntheticEvent.
123
+ * @param {Object } data - All props and the new open flag value in the edit text.
124
+ */
125
+ onOpenChange ?: ComponentEventHandler < DropdownProps >
126
+
117
127
/**
118
128
* Callback for change in dropdown search query value.
119
129
* @param {SyntheticEvent } event - React's original SyntheticEvent.
@@ -128,6 +138,9 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
128
138
*/
129
139
onSelectedChange ?: ComponentEventHandler < DropdownProps >
130
140
141
+ /** Defines whether dropdown is displayed. */
142
+ open ?: boolean
143
+
131
144
/** A placeholder message for the input field. */
132
145
placeholder ?: string
133
146
@@ -172,8 +185,8 @@ export interface DropdownState {
172
185
activeSelectedIndex : number
173
186
defaultHighlightedIndex : number
174
187
focused : boolean
175
- isOpen ? : boolean
176
- searchQuery ? : string
188
+ open : boolean
189
+ searchQuery : string
177
190
value : ShorthandValue | ShorthandCollection
178
191
}
179
192
@@ -204,6 +217,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
204
217
clearable : PropTypes . bool ,
205
218
clearIndicator : customPropTypes . itemShorthand ,
206
219
defaultActiveSelectedIndex : PropTypes . number ,
220
+ defaultOpen : PropTypes . bool ,
207
221
defaultSearchQuery : PropTypes . string ,
208
222
defaultValue : PropTypes . oneOfType ( [
209
223
customPropTypes . itemShorthand ,
@@ -219,8 +233,10 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
219
233
loadingMessage : customPropTypes . itemShorthand ,
220
234
multiple : PropTypes . bool ,
221
235
noResultsMessage : customPropTypes . itemShorthand ,
236
+ onOpenChange : PropTypes . func ,
222
237
onSearchQueryChange : PropTypes . func ,
223
238
onSelectedChange : PropTypes . func ,
239
+ open : PropTypes . bool ,
224
240
placeholder : PropTypes . string ,
225
241
renderItem : PropTypes . func ,
226
242
renderSelectedItem : PropTypes . func ,
@@ -250,7 +266,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
250
266
triggerButton : { } ,
251
267
}
252
268
253
- static autoControlledProps = [ 'activeSelectedIndex' , 'searchQuery' , 'value' ]
269
+ static autoControlledProps = [ 'activeSelectedIndex' , 'open' , ' searchQuery', 'value' ]
254
270
255
271
static Item = DropdownItem
256
272
static SearchInput = DropdownSearchInput
@@ -262,6 +278,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
262
278
// used on single selection to open the dropdown with the selected option as highlighted.
263
279
defaultHighlightedIndex : this . props . multiple ? undefined : null ,
264
280
focused : false ,
281
+ open : false ,
265
282
searchQuery : search ? '' : undefined ,
266
283
value : multiple ? [ ] : null ,
267
284
}
@@ -284,11 +301,12 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
284
301
itemToString,
285
302
toggleIndicator,
286
303
} = this . props
287
- const { defaultHighlightedIndex, searchQuery, value } = this . state
304
+ const { defaultHighlightedIndex, open , searchQuery, value } = this . state
288
305
289
306
return (
290
307
< ElementType className = { classes . root } { ...unhandledProps } >
291
308
< Downshift
309
+ isOpen = { open }
292
310
onChange = { this . handleSelectedChange }
293
311
onInputValueChange = { this . handleSearchQueryChange }
294
312
inputValue = { search ? searchQuery : null }
@@ -305,7 +323,6 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
305
323
getMenuProps,
306
324
getRootProps,
307
325
getToggleButtonProps,
308
- isOpen,
309
326
toggleMenu,
310
327
highlightedIndex,
311
328
selectItemAtIndex,
@@ -320,7 +337,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
320
337
< Ref innerRef = { innerRef } >
321
338
< div
322
339
className = { cx ( Dropdown . slotClassNames . container , classes . container ) }
323
- onClick = { search && ! isOpen ? this . handleContainerClick : undefined }
340
+ onClick = { search && ! open ? this . handleContainerClick : undefined }
324
341
>
325
342
< div
326
343
ref = { this . selectedItemsRef }
@@ -354,7 +371,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
354
371
} )
355
372
: Indicator . create ( toggleIndicator , {
356
373
defaultProps : {
357
- direction : isOpen ? 'top' : 'bottom' ,
374
+ direction : open ? 'top' : 'bottom' ,
358
375
styles : styles . toggleIndicator ,
359
376
} ,
360
377
overrideProps : ( predefinedProps : IndicatorProps ) => ( {
@@ -367,7 +384,6 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
367
384
{ this . renderItemsList (
368
385
styles ,
369
386
variables ,
370
- isOpen ,
371
387
highlightedIndex ,
372
388
toggleMenu ,
373
389
selectItemAtIndex ,
@@ -458,7 +474,6 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
458
474
private renderItemsList (
459
475
styles : ComponentSlotStylesInput ,
460
476
variables : ComponentVariablesInput ,
461
- isOpen : boolean ,
462
477
highlightedIndex : number ,
463
478
toggleMenu : ( ) => void ,
464
479
selectItemAtIndex : ( index : number ) => void ,
@@ -467,6 +482,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
467
482
getInputProps : ( options ?: GetInputPropsOptions ) => any ,
468
483
) {
469
484
const { search } = this . props
485
+ const { open } = this . state
470
486
const { innerRef, ...accessibilityMenuProps } = getMenuProps (
471
487
{ refKey : 'innerRef' } ,
472
488
{ suppressRefError : true } ,
@@ -501,8 +517,8 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
501
517
{ ...accessibilityMenuProps }
502
518
styles = { styles . list }
503
519
tabIndex = { search ? undefined : - 1 } // needs to be focused when trigger button is activated.
504
- aria-hidden = { ! isOpen }
505
- items = { isOpen ? this . renderItems ( styles , variables , getItemProps , highlightedIndex ) : [ ] }
520
+ aria-hidden = { ! open }
521
+ items = { open ? this . renderItems ( styles , variables , getItemProps , highlightedIndex ) : [ ] }
506
522
/>
507
523
</ Ref >
508
524
)
@@ -576,13 +592,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
576
592
}
577
593
578
594
private handleSearchQueryChange = ( searchQuery : string ) => {
579
- this . trySetState ( { searchQuery } )
580
- _ . invoke (
581
- this . props ,
582
- 'onSearchQueryChange' ,
583
- { } , // we don't have event for it, but want to keep the event handling interface, event is empty.
584
- { ...this . props , searchQuery } ,
585
- )
595
+ this . trySetStateAndInvokeHandler ( 'onSearchQueryChange' , null , { searchQuery } )
586
596
}
587
597
588
598
private handleDownshiftStateChanges = (
@@ -602,8 +612,8 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
602
612
}
603
613
604
614
private handleStateChange = ( changes : StateChangeOptions < ShorthandValue > ) => {
605
- if ( changes . isOpen !== undefined && changes . isOpen !== this . state . isOpen ) {
606
- this . setState ( { isOpen : changes . isOpen } )
615
+ if ( changes . isOpen !== undefined && changes . isOpen !== this . state . open ) {
616
+ this . trySetStateAndInvokeHandler ( 'onOpenChange' , null , { open : changes . isOpen } )
607
617
}
608
618
609
619
if ( changes . isOpen && ! this . props . search ) {
@@ -664,19 +674,18 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
664
674
item : ShorthandValue ,
665
675
rtl : boolean ,
666
676
) => ( {
667
- onRemove : ( e : React . SyntheticEvent , DropdownSelectedItemProps : DropdownSelectedItemProps ) => {
668
- this . handleSelectedItemRemove ( e , item , predefinedProps , DropdownSelectedItemProps )
677
+ onRemove : ( e : React . SyntheticEvent , dropdownSelectedItemProps : DropdownSelectedItemProps ) => {
678
+ this . handleSelectedItemRemove ( e , item , predefinedProps , dropdownSelectedItemProps )
669
679
} ,
670
- onClick : ( e : React . SyntheticEvent , DropdownSelectedItemProps : DropdownSelectedItemProps ) => {
680
+ onClick : ( e : React . SyntheticEvent , dropdownSelectedItemProps : DropdownSelectedItemProps ) => {
671
681
const { value } = this . state as { value : ShorthandCollection }
672
- this . trySetState ( {
673
- activeSelectedIndex : value . indexOf ( item ) ,
674
- } )
682
+
683
+ this . trySetState ( { activeSelectedIndex : value . indexOf ( item ) } )
675
684
e . stopPropagation ( )
676
- _ . invoke ( predefinedProps , 'onClick' , e , DropdownSelectedItemProps )
685
+ _ . invoke ( predefinedProps , 'onClick' , e , dropdownSelectedItemProps )
677
686
} ,
678
- onKeyDown : ( e : React . SyntheticEvent , DropdownSelectedItemProps : DropdownSelectedItemProps ) => {
679
- this . handleSelectedItemKeyDown ( e , item , predefinedProps , DropdownSelectedItemProps , rtl )
687
+ onKeyDown : ( e : React . SyntheticEvent , dropdownSelectedItemProps : DropdownSelectedItemProps ) => {
688
+ this . handleSelectedItemKeyDown ( e , item , predefinedProps , dropdownSelectedItemProps , rtl )
680
689
} ,
681
690
} )
682
691
@@ -846,12 +855,11 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
846
855
847
856
private handleSelectedChange = ( item : ShorthandValue ) => {
848
857
const { items, multiple, getA11ySelectionMessage } = this . props
849
- const newState = {
858
+
859
+ this . trySetStateAndInvokeHandler ( 'onSelectedChange' , null , {
850
860
value : multiple ? [ ...( this . state . value as ShorthandCollection ) , item ] : item ,
851
861
searchQuery : this . getSelectedItemAsString ( item ) ,
852
- }
853
-
854
- this . trySetState ( newState )
862
+ } )
855
863
856
864
if ( ! multiple ) {
857
865
this . setState ( { defaultHighlightedIndex : items . indexOf ( item ) } )
@@ -870,9 +878,6 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
870
878
}
871
879
872
880
this . tryFocusTriggerButton ( )
873
-
874
- // we don't have event for it, but want to keep the event handling interface, event is empty.
875
- _ . invoke ( this . props , 'onSelectedChange' , { } , { ...this . props , ...newState } )
876
881
}
877
882
878
883
private handleSelectedItemKeyDown (
@@ -896,21 +901,15 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
896
901
break
897
902
case previousKey :
898
903
if ( value . length > 0 && ! _ . isNil ( activeSelectedIndex ) && activeSelectedIndex > 0 ) {
899
- this . trySetState ( {
900
- activeSelectedIndex : activeSelectedIndex - 1 ,
901
- } )
904
+ this . trySetState ( { activeSelectedIndex : activeSelectedIndex - 1 } )
902
905
}
903
906
break
904
907
case nextKey :
905
908
if ( value . length > 0 && ! _ . isNil ( activeSelectedIndex ) ) {
906
909
if ( activeSelectedIndex < value . length - 1 ) {
907
- this . trySetState ( {
908
- activeSelectedIndex : activeSelectedIndex + 1 ,
909
- } )
910
+ this . trySetState ( { activeSelectedIndex : activeSelectedIndex + 1 } )
910
911
} else {
911
- this . trySetState ( {
912
- activeSelectedIndex : null ,
913
- } )
912
+ this . trySetState ( { activeSelectedIndex : null } )
914
913
if ( this . props . search ) {
915
914
e . preventDefault ( ) // prevents caret to forward one position in input.
916
915
this . inputRef . current . focus ( )
@@ -932,9 +931,7 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
932
931
predefinedProps : DropdownSelectedItemProps ,
933
932
DropdownSelectedItemProps : DropdownSelectedItemProps ,
934
933
) {
935
- this . trySetState ( {
936
- activeSelectedIndex : null ,
937
- } )
934
+ this . trySetState ( { activeSelectedIndex : null } )
938
935
this . removeItemFromValue ( item )
939
936
this . tryFocusSearchInput ( )
940
937
this . tryFocusTriggerButton ( )
@@ -953,14 +950,25 @@ class Dropdown extends AutoControlledComponent<Extendable<DropdownProps>, Dropdo
953
950
poppedItem = value . pop ( )
954
951
}
955
952
956
- this . trySetState ( { value } )
957
-
958
953
if ( getA11ySelectionMessage && getA11ySelectionMessage . onRemove ) {
959
954
this . setA11yStatus ( getA11ySelectionMessage . onRemove ( poppedItem ) )
960
955
}
961
956
962
- // we don't have event for it, but want to keep the event handling interface, event is empty.
963
- _ . invoke ( this . props , 'onSelectedChange' , { } , { ...this . props , value } )
957
+ this . trySetStateAndInvokeHandler ( 'onSelectedChange' , null , { value } )
958
+ }
959
+
960
+ /**
961
+ * Calls trySetState (for autoControlledProps) and invokes event handler exposed to user.
962
+ * We don't have the event object for most events coming from Downshift se we send an empty event
963
+ * because we want to keep the event handling interface
964
+ */
965
+ private trySetStateAndInvokeHandler = (
966
+ handlerName : keyof DropdownProps ,
967
+ event : React . SyntheticEvent < HTMLElement > ,
968
+ newState : Partial < DropdownState > ,
969
+ ) => {
970
+ this . trySetState ( newState )
971
+ _ . invoke ( this . props , handlerName , event , { ...this . props , ...newState } )
964
972
}
965
973
966
974
private tryFocusTriggerButton = ( ) => {
0 commit comments