@@ -8,14 +8,14 @@ interface DropdownOption {
88 * Implements the dropdown inputs used in the Git Graph View's top control bar.
99 */
1010class Dropdown {
11+ private readonly showInfo : boolean ;
12+ private readonly multipleAllowed : boolean ;
13+ private readonly changeCallback : ( values : string [ ] ) => void ;
14+
1115 private options : ReadonlyArray < DropdownOption > = [ ] ;
1216 private optionsSelected : boolean [ ] = [ ] ;
13- private lastSelected : number = 0 ;
14- private numSelected : number = 0 ;
17+ private lastSelected : number = 0 ; // Only used when multipleAllowed === false
1518 private dropdownVisible : boolean = false ;
16- private showInfo : boolean ;
17- private multipleAllowed : boolean ;
18- private changeCallback : { ( values : string [ ] ) : void } ;
1919 private lastClicked : number = 0 ;
2020 private doubleClickTimeout : NodeJS . Timer | null = null ;
2121
@@ -35,7 +35,7 @@ class Dropdown {
3535 * @param changeCallback A callback to be invoked when the selected item(s) of the dropdown changes.
3636 * @returns The Dropdown instance.
3737 */
38- constructor ( id : string , showInfo : boolean , multipleAllowed : boolean , dropdownType : string , changeCallback : { ( values : string [ ] ) : void } ) {
38+ constructor ( id : string , showInfo : boolean , multipleAllowed : boolean , dropdownType : string , changeCallback : ( values : string [ ] ) => void ) {
3939 this . showInfo = showInfo ;
4040 this . multipleAllowed = multipleAllowed ;
4141 this . changeCallback = changeCallback ;
@@ -78,9 +78,9 @@ class Dropdown {
7878 if ( ( < HTMLElement > e . target ) . closest ( '.dropdown' ) !== this . elem ) {
7979 this . close ( ) ;
8080 } else {
81- let option = < HTMLElement | null > ( < HTMLElement > e . target ) . closest ( '.dropdownOption' ) ;
81+ const option = < HTMLElement | null > ( < HTMLElement > e . target ) . closest ( '.dropdownOption' ) ;
8282 if ( option !== null && option . parentNode === this . optionsElem && typeof option . dataset . id !== 'undefined' ) {
83- this . selectOption ( parseInt ( option . dataset . id ! ) ) ;
83+ this . onOptionClick ( parseInt ( option . dataset . id ! ) ) ;
8484 }
8585 }
8686 }
@@ -97,27 +97,104 @@ class Dropdown {
9797 public setOptions ( options : ReadonlyArray < DropdownOption > , optionsSelected : string [ ] ) {
9898 this . options = options ;
9999 this . optionsSelected = [ ] ;
100- this . numSelected = 0 ;
101100 let selectedOption = - 1 , isSelected ;
102101 for ( let i = 0 ; i < options . length ; i ++ ) {
103102 isSelected = optionsSelected . includes ( options [ i ] . value ) ;
104103 this . optionsSelected [ i ] = isSelected ;
105104 if ( isSelected ) {
106105 selectedOption = i ;
107- this . numSelected ++ ;
108106 }
109107 }
110108 if ( selectedOption === - 1 ) {
111109 selectedOption = 0 ;
112110 this . optionsSelected [ selectedOption ] = true ;
113- this . numSelected ++ ;
114111 }
115112 this . lastSelected = selectedOption ;
116113 if ( this . dropdownVisible && options . length <= 1 ) this . close ( ) ;
117114 this . render ( ) ;
118115 this . clearDoubleClickTimeout ( ) ;
119116 }
120117
118+ /**
119+ * Is a value selected in the dropdown (respecting "Show All")
120+ * @param value The value to check.
121+ * @returns TRUE => The value is selected, FALSE => The value is not selected.
122+ */
123+ public isSelected ( value : string ) {
124+ if ( this . options . length > 0 ) {
125+ if ( this . multipleAllowed && this . optionsSelected [ 0 ] ) {
126+ // Multiple options can be selected, and "Show All" is selected.
127+ return true ;
128+ }
129+ const optionIndex = this . options . findIndex ( ( option ) => option . value === value ) ;
130+ if ( optionIndex > - 1 && this . optionsSelected [ optionIndex ] ) {
131+ // The specific option is selected
132+ return true ;
133+ }
134+ }
135+ return false ;
136+ }
137+
138+ /**
139+ * Select a specific value in the dropdown.
140+ * @param value The value to select.
141+ */
142+ public selectOption ( value : string ) {
143+ const optionIndex = this . options . findIndex ( ( option ) => value === option . value ) ;
144+ if ( this . multipleAllowed && optionIndex > - 1 && ! this . optionsSelected [ optionIndex ] ) {
145+ // Select the option with the specified value
146+ this . optionsSelected [ optionIndex ] = true ;
147+
148+ if ( ! this . optionsSelected [ 0 ] && this . optionsSelected . slice ( 1 ) . every ( ( selected ) => selected ) ) {
149+ // All options are selected, so simplify selected items to just be "Show All"
150+ this . optionsSelected [ 0 ] = true ;
151+ for ( let i = 1 ; i < this . optionsSelected . length ; i ++ ) {
152+ this . optionsSelected [ i ] = false ;
153+ }
154+ }
155+
156+ // A change has occurred, re-render the dropdown options
157+ const menuScroll = this . menuElem . scrollTop ;
158+ this . render ( ) ;
159+ if ( this . dropdownVisible ) {
160+ this . menuElem . scroll ( 0 , menuScroll ) ;
161+ }
162+ this . changeCallback ( this . getSelectedOptions ( false ) ) ;
163+ }
164+ }
165+
166+ /**
167+ * Unselect a specific value in the dropdown.
168+ * @param value The value to unselect.
169+ */
170+ public unselectOption ( value : string ) {
171+ const optionIndex = this . options . findIndex ( ( option ) => value === option . value ) ;
172+ if ( this . multipleAllowed && optionIndex > - 1 && ( this . optionsSelected [ 0 ] || this . optionsSelected [ optionIndex ] ) ) {
173+ if ( this . optionsSelected [ 0 ] ) {
174+ // Show All is currently selected, so unselect it, and select all branch options
175+ this . optionsSelected [ 0 ] = false ;
176+ for ( let i = 1 ; i < this . optionsSelected . length ; i ++ ) {
177+ this . optionsSelected [ i ] = true ;
178+ }
179+ }
180+
181+ // Unselect the option with the specified value
182+ this . optionsSelected [ optionIndex ] = false ;
183+ if ( this . optionsSelected . every ( selected => ! selected ) ) {
184+ // All items have been unselected, select "Show All"
185+ this . optionsSelected [ 0 ] = true ;
186+ }
187+
188+ // A change has occurred, re-render the dropdown options
189+ const menuScroll = this . menuElem . scrollTop ;
190+ this . render ( ) ;
191+ if ( this . dropdownVisible ) {
192+ this . menuElem . scroll ( 0 , menuScroll ) ;
193+ }
194+ this . changeCallback ( this . getSelectedOptions ( false ) ) ;
195+ }
196+ }
197+
121198 /**
122199 * Refresh the rendered dropdown to apply style changes.
123200 */
@@ -209,7 +286,7 @@ class Dropdown {
209286 * Select a dropdown option.
210287 * @param option The index of the option to select.
211288 */
212- private selectOption ( option : number ) {
289+ private onOptionClick ( option : number ) {
213290 // Note: Show All is always the first option (0 index) when multiple selected items are allowed
214291 let change = false ;
215292 let doubleClick = this . doubleClickTimeout !== null && this . lastClicked === option ;
@@ -218,10 +295,8 @@ class Dropdown {
218295 if ( doubleClick ) {
219296 // Double click
220297 if ( this . multipleAllowed && option === 0 ) {
221- this . numSelected = 1 ;
222298 for ( let i = 1 ; i < this . optionsSelected . length ; i ++ ) {
223299 this . optionsSelected [ i ] = ! this . optionsSelected [ i ] ;
224- if ( this . optionsSelected [ i ] ) this . numSelected ++ ;
225300 }
226301 change = true ;
227302 }
@@ -236,27 +311,22 @@ class Dropdown {
236311 for ( let i = 1 ; i < this . optionsSelected . length ; i ++ ) {
237312 this . optionsSelected [ i ] = false ;
238313 }
239- this . numSelected = 1 ;
240314 change = true ;
241315 }
242316 } else {
243317 if ( this . optionsSelected [ 0 ] ) {
318+ // Deselect "Show All" if it is enabled
244319 this . optionsSelected [ 0 ] = false ;
245- this . numSelected -- ;
246320 }
247321
248- this . numSelected += this . optionsSelected [ option ] ? - 1 : 1 ;
249322 this . optionsSelected [ option ] = ! this . optionsSelected [ option ] ;
250323
251- if ( this . numSelected === 0 ) {
324+ if ( this . optionsSelected . every ( selected => ! selected ) ) {
325+ // All items have been unselected, select "Show All"
252326 this . optionsSelected [ 0 ] = true ;
253- this . numSelected = 1 ;
254327 }
255328 change = true ;
256329 }
257- if ( change && this . optionsSelected [ option ] ) {
258- this . lastSelected = option ;
259- }
260330 } else {
261331 // Only a single dropdown option can be selected
262332 this . close ( ) ;
0 commit comments