1
1
import * as React from 'react'
2
2
import * as PropTypes from 'prop-types'
3
3
import * as _ from 'lodash'
4
+ import cx from 'classnames'
4
5
5
6
import {
6
7
Extendable ,
@@ -140,6 +141,7 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
140
141
}
141
142
142
143
export interface DropdownState {
144
+ isOpen ?: boolean
143
145
value : ShorthandValue | ShorthandValue [ ]
144
146
backspaceDelete : boolean
145
147
focused : boolean
@@ -160,6 +162,7 @@ export default class Dropdown extends AutoControlledComponent<
160
162
private buttonRef = React . createRef < HTMLElement > ( )
161
163
private inputRef = React . createRef < HTMLElement > ( )
162
164
private listRef = React . createRef < HTMLElement > ( )
165
+ private selectedItemsRef = React . createRef < HTMLDivElement > ( )
163
166
164
167
static displayName = 'Dropdown'
165
168
@@ -199,7 +202,7 @@ export default class Dropdown extends AutoControlledComponent<
199
202
] ) ,
200
203
}
201
204
202
- static defaultProps = {
205
+ static defaultProps : DropdownProps = {
203
206
as : 'div' ,
204
207
itemToString : item => {
205
208
if ( ! item || React . isValidElement ( item ) ) {
@@ -256,11 +259,7 @@ export default class Dropdown extends AutoControlledComponent<
256
259
selectedItem = { search && ! multiple ? undefined : null }
257
260
getA11yStatusMessage = { getA11yStatusMessage }
258
261
defaultHighlightedIndex = { defaultHighlightedIndex }
259
- onStateChange = { changes => {
260
- if ( changes . isOpen && ! search ) {
261
- this . listRef . current . focus ( )
262
- }
263
- } }
262
+ onStateChange = { this . handleStateChange }
264
263
>
265
264
{ ( {
266
265
getInputProps,
@@ -280,19 +279,24 @@ export default class Dropdown extends AutoControlledComponent<
280
279
return (
281
280
< Ref innerRef = { innerRef } >
282
281
< div
283
- className = { classes . container }
282
+ className = { cx ( ` ${ Dropdown . className } __container` , classes . container ) }
284
283
onClick = { multiple ? this . handleContainerClick . bind ( this , isOpen ) : undefined }
285
284
>
286
- { multiple && this . renderSelectedItems ( ) }
287
- { search
288
- ? this . renderSearchInput (
289
- accessibilityRootPropsRest ,
290
- getInputProps ,
291
- highlightedIndex ,
292
- selectItemAtIndex ,
293
- variables ,
294
- )
295
- : this . renderTriggerButton ( styles , getToggleButtonProps ) }
285
+ < div
286
+ ref = { this . selectedItemsRef }
287
+ className = { cx ( `${ Dropdown . className } __selected-items` , classes . selectedItems ) }
288
+ >
289
+ { multiple && this . renderSelectedItems ( ) }
290
+ { search
291
+ ? this . renderSearchInput (
292
+ accessibilityRootPropsRest ,
293
+ getInputProps ,
294
+ highlightedIndex ,
295
+ selectItemAtIndex ,
296
+ variables ,
297
+ )
298
+ : this . renderTriggerButton ( styles , getToggleButtonProps ) }
299
+ </ div >
296
300
{ Indicator . create ( toggleIndicator , {
297
301
defaultProps : {
298
302
direction : isOpen ? 'top' : 'bottom' ,
@@ -359,7 +363,7 @@ export default class Dropdown extends AutoControlledComponent<
359
363
) => void ,
360
364
variables ,
361
365
) : JSX . Element {
362
- const { searchInput, multiple, placeholder, toggleIndicator } = this . props
366
+ const { searchInput, multiple, placeholder } = this . props
363
367
const { searchQuery, value } = this . state
364
368
365
369
const noPlaceholder =
@@ -368,7 +372,6 @@ export default class Dropdown extends AutoControlledComponent<
368
372
return DropdownSearchInput . create ( searchInput || { } , {
369
373
defaultProps : {
370
374
placeholder : noPlaceholder ? '' : placeholder ,
371
- hasToggleButton : ! ! toggleIndicator ,
372
375
variables,
373
376
inputRef : this . inputRef ,
374
377
} ,
@@ -394,14 +397,16 @@ export default class Dropdown extends AutoControlledComponent<
394
397
getItemProps : ( options : GetItemPropsOptions < ShorthandValue > ) => any ,
395
398
getInputProps : ( options ?: GetInputPropsOptions ) => any ,
396
399
) {
400
+ const { search } = this . props
397
401
const { innerRef, ...accessibilityMenuProps } = getMenuProps (
398
402
{ refKey : 'innerRef' } ,
399
403
{ suppressRefError : true } ,
400
404
)
401
- const { search } = this . props
405
+
402
406
// If it's just a selection, some attributes and listeners from Downshift input need to go on the menu list.
403
407
if ( ! search ) {
404
408
const accessibilityInputProps = getInputProps ( )
409
+
405
410
accessibilityMenuProps [ 'aria-activedescendant' ] =
406
411
accessibilityInputProps [ 'aria-activedescendant' ]
407
412
accessibilityMenuProps [ 'onKeyDown' ] = e => {
@@ -440,8 +445,8 @@ export default class Dropdown extends AutoControlledComponent<
440
445
highlightedIndex : number ,
441
446
) {
442
447
const { loading, loadingMessage, noResultsMessage, renderItem } = this . props
443
-
444
448
const filteredItems = this . getItemsFilteredBySearchQuery ( )
449
+
445
450
const items = _ . map ( filteredItems , ( item , index ) =>
446
451
DropdownItem . create ( item , {
447
452
defaultProps : {
@@ -528,6 +533,16 @@ export default class Dropdown extends AutoControlledComponent<
528
533
}
529
534
}
530
535
536
+ private handleStateChange = ( changes : StateChangeOptions < ShorthandValue > ) => {
537
+ if ( changes . isOpen !== undefined && changes . isOpen !== this . state . isOpen ) {
538
+ this . setState ( { isOpen : changes . isOpen } )
539
+ }
540
+
541
+ if ( changes . isOpen && ! this . props . search ) {
542
+ this . listRef . current . focus ( )
543
+ }
544
+ }
545
+
531
546
private getItemsFilteredBySearchQuery = ( ) : ShorthandValue [ ] => {
532
547
const { items, itemToString, multiple, search } = this . props
533
548
const { searchQuery, value } = this . state
@@ -536,10 +551,12 @@ export default class Dropdown extends AutoControlledComponent<
536
551
if ( multiple ) {
537
552
filteredItems = _ . difference ( filteredItems , value as ShorthandValue [ ] )
538
553
}
554
+
539
555
if ( search ) {
540
556
if ( _ . isFunction ( search ) ) {
541
557
return search ( filteredItems , searchQuery )
542
558
}
559
+
543
560
return filteredItems . filter (
544
561
item =>
545
562
itemToString ( item )
@@ -572,9 +589,7 @@ export default class Dropdown extends AutoControlledComponent<
572
589
item : ShorthandValue ,
573
590
index : number ,
574
591
getItemProps : ( options : GetItemPropsOptions < ShorthandValue > ) => any ,
575
- ) => ( {
576
- accessibilityItemProps : getItemProps ( { item, index } ) ,
577
- } )
592
+ ) => ( { accessibilityItemProps : getItemProps ( { item, index } ) } )
578
593
579
594
private handleSelectedItemOverrides = (
580
595
predefinedProps : DropdownSelectedItemProps ,
@@ -606,9 +621,9 @@ export default class Dropdown extends AutoControlledComponent<
606
621
searchInputProps : DropdownSearchInputProps ,
607
622
) => {
608
623
this . setState ( { focused : false } )
609
-
610
624
_ . invoke ( predefinedProps , 'onInputBlur' , e , searchInputProps )
611
625
}
626
+
612
627
const handleInputKeyDown = (
613
628
e : React . SyntheticEvent ,
614
629
searchInputProps : DropdownSearchInputProps ,
@@ -700,21 +715,27 @@ export default class Dropdown extends AutoControlledComponent<
700
715
}
701
716
702
717
private handleSelectedChange = ( item : ShorthandValue ) => {
703
- const { multiple, getA11ySelectionMessage, search } = this . props
718
+ const { items , multiple, getA11ySelectionMessage, search } = this . props
704
719
const newValue = multiple ? [ ...( this . state . value as ShorthandValue [ ] ) , item ] : item
705
720
706
- this . trySetState ( {
707
- value : newValue ,
708
- searchQuery : '' ,
709
- } )
710
- if ( ! this . props . search && ! this . props . multiple ) {
711
- this . setState ( {
712
- defaultHighlightedIndex : this . props . items . indexOf ( item ) ,
713
- } )
721
+ this . trySetState ( { value : newValue , searchQuery : '' } )
722
+
723
+ if ( ! search && ! multiple ) {
724
+ this . setState ( { defaultHighlightedIndex : items . indexOf ( item ) } )
714
725
}
726
+
715
727
if ( getA11ySelectionMessage && getA11ySelectionMessage . onAdd ) {
716
728
this . setA11yStatus ( getA11ySelectionMessage . onAdd ( item ) )
717
729
}
730
+
731
+ if ( multiple ) {
732
+ setTimeout (
733
+ ( ) =>
734
+ ( this . selectedItemsRef . current . scrollTop = this . selectedItemsRef . current . scrollHeight ) ,
735
+ 0 ,
736
+ )
737
+ }
738
+
718
739
if ( ! search ) {
719
740
this . buttonRef . current . focus ( )
720
741
}
@@ -745,9 +766,8 @@ export default class Dropdown extends AutoControlledComponent<
745
766
poppedItem = value . pop ( )
746
767
}
747
768
748
- this . trySetState ( {
749
- value,
750
- } )
769
+ this . trySetState ( { value } )
770
+
751
771
if ( getA11ySelectionMessage && getA11ySelectionMessage . onRemove ) {
752
772
this . setA11yStatus ( getA11ySelectionMessage . onRemove ( poppedItem ) )
753
773
}
0 commit comments