@ -2,18 +2,19 @@ import {createGroup} from 'app/client/components/commands';
import { ACIndexImpl , ACItem , ACResults , buildHighlightedDom , HighlightFunc } from 'app/client/lib/ACIndex' ;
import { ACIndexImpl , ACItem , ACResults , buildHighlightedDom , HighlightFunc } from 'app/client/lib/ACIndex' ;
import { IAutocompleteOptions } from 'app/client/lib/autocomplete' ;
import { IAutocompleteOptions } from 'app/client/lib/autocomplete' ;
import { IToken , TokenField , tokenFieldStyles } from 'app/client/lib/TokenField' ;
import { IToken , TokenField , tokenFieldStyles } from 'app/client/lib/TokenField' ;
import { colors , testId , vars } from 'app/client/ui2018/cssVars' ;
import { colors , testId } from 'app/client/ui2018/cssVars' ;
import { menuCssClass } from 'app/client/ui2018/menus' ;
import { menuCssClass } from 'app/client/ui2018/menus' ;
import { cssInvalidToken } from 'app/client/widgets/ChoiceListCell' ;
import { cssInvalidToken } from 'app/client/widgets/ChoiceListCell' ;
import { createMobileButtons , getButtonMargins } from 'app/client/widgets/EditorButtons' ;
import { createMobileButtons , getButtonMargins } from 'app/client/widgets/EditorButtons' ;
import { EditorPlacement } from 'app/client/widgets/EditorPlacement' ;
import { EditorPlacement } from 'app/client/widgets/EditorPlacement' ;
import { NewBaseEditor , Options } from 'app/client/widgets/NewBaseEditor' ;
import { NewBaseEditor , Options } from 'app/client/widgets/NewBaseEditor' ;
import { cssPlusButton , cssPlusIcon , cssRefList } from 'app/client/widgets/ReferenceEditor' ;
import { csvEncodeRow } from 'app/common/csvFormat' ;
import { csvEncodeRow } from 'app/common/csvFormat' ;
import { CellValue } from "app/common/DocActions" ;
import { CellValue } from "app/common/DocActions" ;
import { decodeObject , encodeObject } from 'app/plugin/objtypes' ;
import { decodeObject , encodeObject } from 'app/plugin/objtypes' ;
import { dom , styled } from 'grainjs' ;
import { dom , styled } from 'grainjs' ;
import { ChoiceOptions , getFillColor , getTextColor } from 'app/client/widgets/ChoiceTextBox' ;
import { ChoiceOptions , getFillColor , getTextColor } from 'app/client/widgets/ChoiceTextBox' ;
import { choiceToken , cssChoiceACItem } from 'app/client/widgets/ChoiceToken' ;
import { icon } from 'app/client/ui2018/icons' ;
export class ChoiceItem implements ACItem , IToken {
export class ChoiceItem implements ACItem , IToken {
public cleanText : string = this . label . toLowerCase ( ) . trim ( ) ;
public cleanText : string = this . label . toLowerCase ( ) . trim ( ) ;
@ -36,8 +37,8 @@ export class ChoiceListEditor extends NewBaseEditor {
private _inputSizer : HTMLElement ; // Part of _contentSizer to size the text input
private _inputSizer : HTMLElement ; // Part of _contentSizer to size the text input
private _alignment : string ;
private _alignment : string ;
// Whether to include a button to show a new choice. (It would make sense to disable it when
// Whether to include a button to show a new choice.
// user cannot change th e column configuration.)
// TODO: Disable when the user cannot change column configuration.
private _enableAddNew : boolean = true ;
private _enableAddNew : boolean = true ;
private _showAddNew : boolean = false ;
private _showAddNew : boolean = false ;
@ -54,7 +55,7 @@ export class ChoiceListEditor extends NewBaseEditor {
const acIndex = new ACIndexImpl < ChoiceItem > ( acItems ) ;
const acIndex = new ACIndexImpl < ChoiceItem > ( acItems ) ;
const acOptions : IAutocompleteOptions < ChoiceItem > = {
const acOptions : IAutocompleteOptions < ChoiceItem > = {
menuCssClass : menuCssClass + ' ' + cssRefList . className + ' ' + cssChoiceList . className + ' test-autocomplete' ,
menuCssClass : ` ${ menuCssClass } ${ cssChoiceList . className } test-autocomplete ` ,
search : async ( term : string ) = > this . _maybeShowAddNew ( acIndex . search ( term ) , term ) ,
search : async ( term : string ) = > this . _maybeShowAddNew ( acIndex . search ( term ) , term ) ,
renderItem : ( item , highlightFunc ) = > this . _renderACItem ( item , highlightFunc ) ,
renderItem : ( item , highlightFunc ) = > this . _renderACItem ( item , highlightFunc ) ,
getItemText : ( item ) = > item . label ,
getItemText : ( item ) = > item . label ,
@ -80,6 +81,7 @@ export class ChoiceListEditor extends NewBaseEditor {
acOptions ,
acOptions ,
openAutocompleteOnFocus : true ,
openAutocompleteOnFocus : true ,
readonly : options . readonly ,
readonly : options . readonly ,
trimLabels : true ,
styles : { cssTokenField , cssToken , cssDeleteButton , cssDeleteIcon } ,
styles : { cssTokenField , cssToken , cssDeleteButton , cssDeleteIcon } ,
} ) ;
} ) ;
@ -146,6 +148,10 @@ export class ChoiceListEditor extends NewBaseEditor {
return this . _textInput . selectionStart || 0 ;
return this . _textInput . selectionStart || 0 ;
}
}
/ * *
* Updates list of valid choices with any new ones that may have been
* added from directly inside the editor ( via the "add new" item in autocomplete ) .
* /
public async prepForSave() {
public async prepForSave() {
const tokens = this . _tokenField . tokensObs . get ( ) ;
const tokens = this . _tokenField . tokensObs . get ( ) ;
const newChoices = tokens . filter ( t = > t . isNew ) . map ( t = > t . label ) ;
const newChoices = tokens . filter ( t = > t . isNew ) . map ( t = > t . label ) ;
@ -202,33 +208,39 @@ export class ChoiceListEditor extends NewBaseEditor {
this . _textInput . style . width = this . _inputSizer . getBoundingClientRect ( ) . width + 'px' ;
this . _textInput . style . width = this . _inputSizer . getBoundingClientRect ( ) . width + 'px' ;
}
}
/ * *
* If the search text does not match anything exactly , adds 'new' item to it .
*
* Also see : prepForSave.
* /
private _maybeShowAddNew ( result : ACResults < ChoiceItem > , text : string ) : ACResults < ChoiceItem > {
private _maybeShowAddNew ( result : ACResults < ChoiceItem > , text : string ) : ACResults < ChoiceItem > {
// If the search text does not match anything exactly, add 'new' item for it. See also prepForSave.
this . _showAddNew = false ;
this . _showAddNew = false ;
if ( this . _enableAddNew && text ) {
const trimmedText = text . trim ( ) ;
const addNewItem = new ChoiceItem ( text , false , true ) ;
if ( ! this . _enableAddNew || ! trimmedText ) { return result ; }
if ( ! result . items . find ( ( item ) = > item . cleanText === addNewItem . cleanText ) ) {
result . items . push ( addNewItem ) ;
const addNewItem = new ChoiceItem ( trimmedText , false , true ) ;
this . _showAddNew = true ;
if ( result . items . find ( ( item ) = > item . cleanText === addNewItem . cleanText ) ) {
}
return result ;
}
}
result . items . push ( addNewItem ) ;
this . _showAddNew = true ;
return result ;
return result ;
}
}
private _renderACItem ( item : ChoiceItem , highlightFunc : HighlightFunc ) {
private _renderACItem ( item : ChoiceItem , highlightFunc : HighlightFunc ) {
const options = this . _choiceOptionsByName [ item . label ] ;
const options = this . _choiceOptionsByName [ item . label ] ;
const fillColor = getFillColor ( options ) ;
const textColor = getTextColor ( options ) ;
return css Item(
return cssChoiceACItem (
( item . isNew ?
( item . isNew ?
[ css Item. cls ( '-new' ) , cssPlusButton ( cssPlusIcon ( 'Plus' ) ) ] :
[ css ChoiceAC Item. cls ( '-new' ) , cssPlusButton ( cssPlusIcon ( 'Plus' ) ) ] :
[ css Item. cls ( '-with-new' , this . _showAddNew ) ]
[ css ChoiceAC Item. cls ( '-with-new' , this . _showAddNew ) ]
) ,
) ,
c ssItemLabel (
c hoiceToken (
buildHighlightedDom ( item . label , highlightFunc , cssMatchText ) ,
buildHighlightedDom ( item . label , highlightFunc , cssMatchText ) ,
dom. style ( 'background-color' , fillColor ) ,
options || { } ,
dom . style ( ' color', textColor ) ,
dom . style ( ' max-width', '100%' ) ,
testId ( 'choice-list-editor-item-label' )
testId ( 'choice-list-editor-item-label' )
) ,
) ,
testId ( 'choice-list-editor-item' ) ,
testId ( 'choice-list-editor-item' ) ,
@ -258,6 +270,7 @@ const cssToken = styled(tokenFieldStyles.cssToken, `
padding : 1px 4 px ;
padding : 1px 4 px ;
margin : 2px ;
margin : 2px ;
line - height : 16px ;
line - height : 16px ;
white - space : pre ;
& . selected {
& . selected {
box - shadow : inset 0 0 0 1 px $ { colors . lightGreen } ;
box - shadow : inset 0 0 0 1 px $ { colors . lightGreen } ;
@ -316,7 +329,10 @@ const cssInputSizer = styled('div', `
// Set z-index to be higher than the 1000 set for .cell_editor.
// Set z-index to be higher than the 1000 set for .cell_editor.
export const cssChoiceList = styled ( 'div' , `
export const cssChoiceList = styled ( 'div' , `
z - index : 1001 ;
z - index : 1001 ;
box - shadow : 0 0 px 8 px 0 rgba ( 38 , 38 , 51 , 0.6 )
box - shadow : 0 0 px 8 px 0 rgba ( 38 , 38 , 51 , 0.6 ) ;
overflow - y : auto ;
padding : 8px 0 0 0 ;
-- weaseljs - menu - item - padding : 8px 16 px ;
` );
` );
const cssReadonlyStyle = styled ( 'div' , `
const cssReadonlyStyle = styled ( 'div' , `
@ -324,48 +340,25 @@ const cssReadonlyStyle = styled('div', `
background : white ;
background : white ;
` );
` );
// We need to know the height of the sticky "+" element.
export const cssMatchText = styled ( 'span' , `
const addNewHeight = '37px' ;
text - decoration : underline ;
export const cssItem = styled ( 'li' , `
display : block ;
font - family : $ { vars . fontFamily } ;
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
outline : none ;
padding : var ( -- weaseljs - menu - item - padding , 8 px 24 px ) ;
cursor : pointer ;
& . selected {
background - color : $ { colors . mediumGreyOpaque } ;
color : $ { colors . dark } ;
}
& - with - new {
scroll - margin - bottom : $ { addNewHeight } ;
}
& - new {
display : flex ;
align - items : center ;
color : $ { colors . slate } ;
position : sticky ;
bottom : 0px ;
height : $ { addNewHeight } ;
background - color : white ;
border - top : 1px solid $ { colors . mediumGreyOpaque } ;
scroll - margin - bottom : initial ;
}
& - new . selected {
color : $ { colors . lightGrey } ;
}
` );
` );
export const css ItemLabel = styled ( 'div' , `
export const cssPlusButton = styled ( 'div' , `
display : inline - block ;
display : inline - block ;
padding : 1px 4 px ;
width : 20px ;
border - radius : 3px ;
height : 20px ;
border - radius : 20px ;
margin - right : 8px ;
text - align : center ;
background - color : $ { colors . lightGreen } ;
color : $ { colors . light } ;
. selected > & {
background - color : $ { colors . darkGreen } ;
}
` );
` );
export const cssMatchText = styled ( 'span' , `
export const cssPlusIcon = styled ( icon , `
text - decoration : underline ;
background - color : $ { colors . light } ;
` );
` );