@@ -10,6 +10,7 @@ interface DropdownOption {
1010class Dropdown {
1111 private readonly showInfo : boolean ;
1212 private readonly multipleAllowed : boolean ;
13+ private readonly selectMultipleWithCtrl : boolean ;
1314 private readonly changeCallback : ( values : string [ ] ) => void ;
1415
1516 private options : ReadonlyArray < DropdownOption > = [ ] ;
@@ -34,10 +35,12 @@ class Dropdown {
3435 * @param dropdownType The type of content the dropdown is being used for.
3536 * @param changeCallback A callback to be invoked when the selected item(s) of the dropdown changes.
3637 * @returns The Dropdown instance.
38+ * @param selectMultipleWithCtrl Select multiple items using Ctrl
3739 */
38- constructor ( id : string , showInfo : boolean , multipleAllowed : boolean , dropdownType : string , changeCallback : ( values : string [ ] ) => void ) {
40+ constructor ( id : string , showInfo : boolean , multipleAllowed : boolean , dropdownType : string , changeCallback : ( values : string [ ] ) => void , selectMultipleWithCtrl : boolean = false ) {
3941 this . showInfo = showInfo ;
4042 this . multipleAllowed = multipleAllowed ;
43+ this . selectMultipleWithCtrl = selectMultipleWithCtrl ;
4144 this . changeCallback = changeCallback ;
4245 this . elem = document . getElementById ( id ) ! ;
4346
@@ -61,7 +64,7 @@ class Dropdown {
6164 this . currentValueElem = this . elem . appendChild ( document . createElement ( 'div' ) ) ;
6265 this . currentValueElem . className = 'dropdownCurrentValue' ;
6366
64- alterClass ( this . elem , 'multi' , multipleAllowed ) ;
67+ alterClass ( this . elem , 'multi' , ( multipleAllowed && ! selectMultipleWithCtrl ) ) ;
6568 this . elem . appendChild ( this . menuElem ) ;
6669
6770 document . addEventListener ( 'click' , ( e ) => {
@@ -80,7 +83,7 @@ class Dropdown {
8083 } else {
8184 const option = < HTMLElement | null > ( < HTMLElement > e . target ) . closest ( '.dropdownOption' ) ;
8285 if ( option !== null && option . parentNode === this . optionsElem && typeof option . dataset . id !== 'undefined' ) {
83- this . onOptionClick ( parseInt ( option . dataset . id ! ) ) ;
86+ this . onOptionClick ( parseInt ( option . dataset . id ! ) , e ) ;
8487 }
8588 }
8689 }
@@ -143,18 +146,24 @@ class Dropdown {
143146 */
144147 public selectOption ( value : string ) {
145148 const optionIndex = this . options . findIndex ( ( option ) => value === option . value ) ;
146- if ( this . multipleAllowed && optionIndex > - 1 && ! this . optionsSelected [ 0 ] && ! this . optionsSelected [ optionIndex ] ) {
149+ if ( optionIndex < 0 && ( this . optionsSelected [ 0 ] || this . optionsSelected [ optionIndex ] ) ) return ;
150+ if ( this . multipleAllowed && ! this . selectMultipleWithCtrl ) {
147151 // Select the option with the specified value
148152 this . optionsSelected [ optionIndex ] = true ;
149-
150- // A change has occurred, re-render the dropdown options
151- const menuScroll = this . menuElem . scrollTop ;
152- this . render ( ) ;
153- if ( this . dropdownVisible ) {
154- this . menuElem . scroll ( 0 , menuScroll ) ;
153+ } else {
154+ for ( let i = 1 ; i < this . optionsSelected . length ; i ++ ) {
155+ this . optionsSelected [ i ] = false ;
155156 }
156- this . changeCallback ( this . getSelectedOptions ( false ) ) ;
157+ this . optionsSelected [ optionIndex ] = true ;
157158 }
159+ // A change has occurred, re-render the dropdown options
160+ const menuScroll = this . menuElem . scrollTop ;
161+ this . render ( ) ;
162+ if ( this . dropdownVisible ) {
163+ this . menuElem . scroll ( 0 , menuScroll ) ;
164+ }
165+ this . changeCallback ( this . getSelectedOptions ( false ) ) ;
166+
158167 }
159168
160169 /**
@@ -163,7 +172,8 @@ class Dropdown {
163172 */
164173 public unselectOption ( value : string ) {
165174 const optionIndex = this . options . findIndex ( ( option ) => value === option . value ) ;
166- if ( this . multipleAllowed && optionIndex > - 1 && ( this . optionsSelected [ 0 ] || this . optionsSelected [ optionIndex ] ) ) {
175+ if ( optionIndex < 0 && ( this . optionsSelected [ 0 ] || this . optionsSelected [ optionIndex ] ) ) return ;
176+ if ( this . multipleAllowed ) {
167177 if ( this . optionsSelected [ 0 ] ) {
168178 // Show All is currently selected, so unselect it, and select all branch options
169179 this . optionsSelected [ 0 ] = false ;
@@ -227,7 +237,7 @@ class Dropdown {
227237 for ( let i = 0 ; i < this . options . length ; i ++ ) {
228238 const escapedName = escapeHtml ( this . options [ i ] . name ) ;
229239 html += '<div class="dropdownOption' + ( this . optionsSelected [ i ] ? ' ' + CLASS_SELECTED : '' ) + '" data-id="' + i + '" title="' + escapedName + '">' +
230- ( this . multipleAllowed && this . optionsSelected [ i ] ? '<div class="dropdownOptionMultiSelected">' + SVG_ICONS . check + '</div>' : '' ) +
240+ ( this . multipleAllowed && ! this . selectMultipleWithCtrl && this . optionsSelected [ i ] ? '<div class="dropdownOptionMultiSelected">' + SVG_ICONS . check + '</div>' : '' ) +
231241 escapedName + ( typeof this . options [ i ] . hint === 'string' && this . options [ i ] . hint !== '' ? '<span class="dropdownOptionHint">' + escapeHtml ( this . options [ i ] . hint ! ) + '</span>' : '' ) +
232242 ( this . showInfo ? '<div class="dropdownOptionInfo" title="' + escapeHtml ( this . options [ i ] . value ) + '">' + SVG_ICONS . info + '</div>' : '' ) +
233243 '</div>' ;
@@ -240,7 +250,7 @@ class Dropdown {
240250 // Width must be at least 138px for the filter element.
241251 // Don't need to add 12px if showing (info icons or multi checkboxes) and the scrollbar isn't needed. The scrollbar isn't needed if: menuElem height + filter input (25px) < 297px
242252 const menuElemRect = this . menuElem . getBoundingClientRect ( ) ;
243- this . currentValueElem . style . width = Math . max ( Math . ceil ( menuElemRect . width ) + ( ( this . showInfo || this . multipleAllowed ) && menuElemRect . height < 272 ? 0 : 12 ) , 138 ) + 'px' ;
253+ this . currentValueElem . style . width = Math . max ( Math . ceil ( menuElemRect . width ) + ( ( this . showInfo || ( this . multipleAllowed && ! this . selectMultipleWithCtrl ) ) && menuElemRect . height < 272 ? 0 : 12 ) , 138 ) + 'px' ;
244254 this . menuElem . style . cssText = 'right:0; overflow-y:auto; max-height:297px;' ; // Max height for the dropdown is [filter (31px) + 9.5 * dropdown item (28px) = 297px]
245255 if ( this . dropdownVisible ) this . filter ( ) ;
246256 }
@@ -280,12 +290,11 @@ class Dropdown {
280290 * Select a dropdown option.
281291 * @param option The index of the option to select.
282292 */
283- private onOptionClick ( option : number ) {
293+ private onOptionClick ( option : number , event ?: MouseEvent ) {
284294 // Note: Show All is always the first option (0 index) when multiple selected items are allowed
285295 let change = false ;
286296 let doubleClick = this . doubleClickTimeout !== null && this . lastClicked === option ;
287297 if ( this . doubleClickTimeout !== null ) this . clearDoubleClickTimeout ( ) ;
288-
289298 if ( doubleClick ) {
290299 // Double click
291300 if ( this . multipleAllowed && option === 0 ) {
@@ -296,7 +305,7 @@ class Dropdown {
296305 }
297306 } else {
298307 // Single Click
299- if ( this . multipleAllowed ) {
308+ if ( this . multipleAllowed && ( ! this . selectMultipleWithCtrl || ( event && ( event . ctrlKey || event . metaKey ) ) ) ) {
300309 // Multiple dropdown options can be selected
301310 if ( option === 0 ) {
302311 // Show All was selected
@@ -324,15 +333,27 @@ class Dropdown {
324333 } else {
325334 // Only a single dropdown option can be selected
326335 this . close ( ) ;
327- if ( this . lastSelected !== option ) {
336+ if ( option === 0 ) {
337+ // Show All was selected
338+ if ( ! this . optionsSelected [ 0 ] ) {
339+ this . optionsSelected [ 0 ] = true ;
340+ for ( let i = 1 ; i < this . optionsSelected . length ; i ++ ) {
341+ this . optionsSelected [ i ] = false ;
342+ }
343+ change = true ;
344+ }
345+ } else if ( this . lastSelected !== option ) {
346+ for ( let i = 0 ; i < this . optionsSelected . length ; i ++ ) {
347+ this . optionsSelected [ i ] = false ;
348+ }
328349 this . optionsSelected [ this . lastSelected ] = false ;
329350 this . optionsSelected [ option ] = true ;
330- this . lastSelected = option ;
331351 change = true ;
332352 }
333353 }
334354
335355 if ( change ) {
356+ this . lastSelected = option ;
336357 // If a change has occurred, trigger the callback
337358 this . changeCallback ( this . getSelectedOptions ( false ) ) ;
338359 }
0 commit comments