@@ -14,11 +14,12 @@ import * as dom from 'vs/base/browser/dom';
14
14
import * as arrays from 'vs/base/common/arrays' ;
15
15
import { IContextViewProvider , AnchorPosition } from 'vs/base/browser/ui/contextview/contextview' ;
16
16
import { List } from 'vs/base/browser/ui/list/listWidget' ;
17
- import { IVirtualDelegate , IRenderer } from 'vs/base/browser/ui/list/list' ;
17
+ import { IVirtualDelegate , IRenderer , IListEvent } from 'vs/base/browser/ui/list/list' ;
18
18
import { domEvent } from 'vs/base/browser/event' ;
19
19
import { ScrollbarVisibility } from 'vs/base/common/scrollable' ;
20
20
import { ISelectBoxDelegate , ISelectBoxOptions , ISelectBoxStyles , ISelectData } from 'vs/base/browser/ui/selectBox/selectBox' ;
21
21
import { isMacintosh } from 'vs/base/common/platform' ;
22
+ import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer' ;
22
23
23
24
const $ = dom . $ ;
24
25
@@ -103,6 +104,11 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
103
104
private widthControlElement : HTMLElement ;
104
105
private _currentSelection : number ;
105
106
private _dropDownPosition : AnchorPosition ;
107
+ private detailsProvider : ( index : number ) => { details : string , isMarkdown : boolean } ;
108
+ private selectionDetailsPane : HTMLElement ;
109
+
110
+
111
+ private _sticky : boolean = false ; // for dev purposes only
106
112
107
113
constructor ( options : string [ ] , selected : number , contextViewProvider : IContextViewProvider , styles : ISelectBoxStyles , selectBoxOptions ?: ISelectBoxOptions ) {
108
114
@@ -153,6 +159,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
153
159
dom . addClass ( this . selectDropDownContainer , 'monaco-select-box-dropdown-padding' ) ;
154
160
// Setup list for drop-down select
155
161
this . createSelectList ( this . selectDropDownContainer ) ;
162
+ this . selectionDetailsPane = dom . append ( this . selectDropDownContainer , $ ( '.select-box-details-pane' ) ) ;
156
163
157
164
// Create span flex box item/div we can measure and control
158
165
let widthControlOuterDiv = dom . append ( this . selectDropDownContainer , $ ( '.select-box-dropdown-container-width-control' ) ) ;
@@ -285,6 +292,10 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
285
292
this . selectList . getHTMLElement ( ) . setAttribute ( 'aria-label' , this . selectBoxOptions . ariaLabel ) ;
286
293
}
287
294
295
+ public setDetailsProvider ( provider : ( index : number ) => { details : string , isMarkdown : boolean } ) : void {
296
+ this . detailsProvider = provider ;
297
+ }
298
+
288
299
public focus ( ) : void {
289
300
if ( this . selectElement ) {
290
301
this . selectElement . focus ( ) ;
@@ -320,6 +331,15 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
320
331
content . push ( `.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${ this . styles . listFocusForeground } !important; }` ) ;
321
332
}
322
333
334
+ if ( ! this . styles . selectBorder . equals ( this . styles . selectBackground ) ) {
335
+ content . push ( `.monaco-select-box-dropdown-container { border: 1px solid ${ this . styles . selectBorder } } ` ) ;
336
+ content . push ( `.monaco-select-box-dropdown-container > .select-box-details-pane { border-top: 1px solid ${ this . styles . selectBorder } } ` ) ;
337
+ }
338
+ else if ( this . styles . selectListBorder ) {
339
+ content . push ( `.monaco-select-box-dropdown-container { border: 1px solid ${ this . styles . selectListBorder } } ` ) ;
340
+ content . push ( `.monaco-select-box-dropdown-container > .select-box-details-pane { border-top: 1px solid ${ this . styles . selectListBorder } } ` ) ;
341
+ }
342
+
323
343
// Hover foreground - ignore for disabled options
324
344
if ( this . styles . listHoverForeground ) {
325
345
content . push ( `.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover { color: ${ this . styles . listHoverForeground } !important; }` ) ;
@@ -370,6 +390,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
370
390
371
391
let listBackground = this . styles . selectListBackground ? this . styles . selectListBackground . toString ( ) : background ;
372
392
this . selectDropDownListContainer . style . backgroundColor = listBackground ;
393
+ this . selectionDetailsPane . style . backgroundColor = listBackground ;
373
394
const optionsBorder = this . styles . focusBorder ? this . styles . focusBorder . toString ( ) : null ;
374
395
this . selectDropDownContainer . style . outlineColor = optionsBorder ;
375
396
this . selectDropDownContainer . style . outlineOffset = '-1px' ;
@@ -543,9 +564,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
543
564
this . selectList . reveal ( this . selectList . getFocus ( ) [ 0 ] || 0 ) ;
544
565
}
545
566
546
- // Set final container height after adjustments
547
- this . selectDropDownContainer . style . height = ( listHeight + verticalPadding ) + 'px' ;
548
-
549
567
// Determine optimal width - min(longest option), opt(parent select, excluding margins), max(ContextView controlled)
550
568
const selectWidth = this . selectElement . offsetWidth ;
551
569
const selectMinWidth = this . setWidthControlElement ( this . widthControlElement ) ;
@@ -556,7 +574,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
556
574
// Maintain focus outline on parent select as well as list container - tabindex for focus
557
575
this . selectDropDownListContainer . setAttribute ( 'tabindex' , '0' ) ;
558
576
dom . toggleClass ( this . selectElement , 'synthetic-focus' , true ) ;
559
- dom . toggleClass ( this . selectDropDownContainer , 'synthetic-focus' , true ) ;
560
577
return true ;
561
578
} else {
562
579
return false ;
@@ -626,7 +643,11 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
626
643
. filter ( ( ) => this . selectList . length > 0 )
627
644
. on ( e => this . onMouseUp ( e ) , this , this . toDispose ) ;
628
645
629
- this . toDispose . push ( this . selectList . onDidBlur ( e => this . onListBlur ( ) ) ) ;
646
+ this . toDispose . push (
647
+ this . selectList . onDidBlur ( e => this . onListBlur ( ) ) ,
648
+ this . selectList . onMouseOver ( e => this . selectList . setFocus ( [ e . index ] ) ) ,
649
+ this . selectList . onFocusChange ( e => this . onListFocus ( e ) )
650
+ ) ;
630
651
631
652
this . selectList . getHTMLElement ( ) . setAttribute ( 'aria-expanded' , 'true' ) ;
632
653
}
@@ -672,7 +693,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
672
693
673
694
// List Exit - passive - implicit no selection change, hide drop-down
674
695
private onListBlur ( ) : void {
675
-
696
+ if ( this . _sticky ) { return ; }
676
697
if ( this . selected !== this . _currentSelection ) {
677
698
// Reset selected to current if no change
678
699
this . select ( this . _currentSelection ) ;
@@ -681,6 +702,48 @@ export class SelectBoxList implements ISelectBoxDelegate, IVirtualDelegate<ISele
681
702
this . hideSelectDropDown ( false ) ;
682
703
}
683
704
705
+
706
+ private renderDescriptionMarkdown ( text : string ) : HTMLElement {
707
+ const cleanRenderedMarkdown = ( element : Node ) => {
708
+ for ( let i = 0 ; i < element . childNodes . length ; i ++ ) {
709
+ const child = element . childNodes . item ( i ) ;
710
+
711
+ const tagName = ( < Element > child ) . tagName && ( < Element > child ) . tagName . toLowerCase ( ) ;
712
+ if ( tagName === 'img' ) {
713
+ element . removeChild ( child ) ;
714
+ } else {
715
+ cleanRenderedMarkdown ( child ) ;
716
+ }
717
+ }
718
+ } ;
719
+
720
+ const renderedMarkdown = renderMarkdown ( { value : text } , {
721
+ actionHandler : this . selectBoxOptions . markdownActionHandler
722
+ } ) ;
723
+
724
+ renderedMarkdown . classList . add ( 'select-box-description-markdown' ) ;
725
+ cleanRenderedMarkdown ( renderedMarkdown ) ;
726
+
727
+ return renderedMarkdown ;
728
+ }
729
+
730
+ // List Focus Change - passive - update details pane with newly focused element's data
731
+ private onListFocus ( e : IListEvent < ISelectOptionItem > ) {
732
+ this . selectionDetailsPane . innerText = '' ;
733
+ const selectedIndex = e . indexes [ 0 ] ;
734
+ let description = this . detailsProvider ? this . detailsProvider ( selectedIndex ) : { details : '' , isMarkdown : false } ;
735
+ if ( description . details ) {
736
+ if ( description . isMarkdown ) {
737
+ this . selectionDetailsPane . appendChild ( this . renderDescriptionMarkdown ( description . details ) ) ;
738
+ } else {
739
+ this . selectionDetailsPane . innerText = description . details ;
740
+ }
741
+ this . selectionDetailsPane . style . display = 'block' ;
742
+ } else {
743
+ this . selectionDetailsPane . style . display = 'none' ;
744
+ }
745
+ }
746
+
684
747
// List keyboard controller
685
748
686
749
// List exit - active - hide ContextView dropdown, reset selection, return focus to parent select
0 commit comments