import { ICellEditorComp , PopupComponent , SuppressKeyboardEventParams } from '@ag-grid-community/core' ; import { IAutocompleteSelectCellEditorParameters , DataFormat , IAutocompleterSettings } from './types' ; import autocomplete from './autocompleter/autocomplete' ; require ( './ag-grid-autocomplete-editor.scss' ) ; const KEY_BACKSPACE = 8 ; const KEY_DELETE = 46 ; const KEY_ENTER = 13 ; const KEY_TAB = 9 ; const KEY_UP = 38 ; const KEY_DOWN = 40 ; const KeysHandled = new Set ( [ KEY_BACKSPACE , KEY_DELETE , KEY_ENTER , KEY_TAB , KEY_UP , KEY_DOWN ] ) ; export default class AutocompleteSelectCellEditor extends PopupComponent implements ICellEditorComp { public currentItem ? : DataFormat ; private focusAfterAttached : boolean = false ; private readonly eInput : HTMLInputElement ; private autocompleter ? : any ; private required : boolean = false ; private stopEditing ? : ( cancel ? : boolean ) => void ; private static getSelectData ( parameters ) { if ( typeof parameters . selectData === 'function' ) { return parameters . selectData ( parameters ) ; } if ( Array . isArray ( parameters . selectData ) ) { return parameters . selectData as Array < DataFormat > ; } return [ ] ; } private static getDefaultAutocompleteSettings ( parameters ) { return { showOnFocus : false , render ( cellEditor , item , value ) { const itemElement = document . createElement ( 'div' ) ; const escapedValue = ( value ?? '' ) . replace ( /[$()*+.?[\\\]^{|}]/g , '\\$&' ) ; const regex = new RegExp ( escapedValue , 'gi' ) ; const fieldItem = document . createElement ( 'span' ) ; fieldItem . innerHTML = item . label . replace ( regex , function strongify ( match ) { return `<strong>${ match }</strong>` ; } ) ; itemElement . append ( fieldItem ) ; cellEditor . addManagedListener ( itemElement , 'mousedown' , ( event ) => { cellEditor . currentItem = item ; event . stopPropagation ( ) ; } ) ; return itemElement ; } , renderGroup ( _ , name ) { const div = document . createElement ( 'div' ) ; div . textContent = name ; div . className = 'group' ; return div ; } , className : 'ag-cell-editor-autocomplete' , minLength : 1 , emptyMsg : 'None' , strict : true , autoselectfirst : true , onFreeTextSelect ( ) { } , onSelect ( cellEditor , item ) { cellEditor . currentItem = item ; } , fetch : ( cellEditor , text , callback ) => { const items = AutocompleteSelectCellEditor . getSelectData ( parameters ) ; const match = text . toLowerCase ( ) || cellEditor . eInput . value . toLowerCase ( ) ; callback ( items . filter ( function caseInsensitiveIncludes ( n ) { return n . label . toLowerCase ( ) . includes ( match ) ; } ) ) ; } , debounceWaitMs : 200 , customize ( _ , input , inputRect , container , maxHeight ) { if ( maxHeight < 100 ) { container . style . top = '10px' ; container . style . bottom = `${ window . innerHeight - inputRect . bottom + input . offsetHeight }px` ; container . style . maxHeight = '140px' ; } } , } ; } private static suppressKeyboardEvent ( parameters ) { const { keyCode } = parameters . event ; return parameters . editing && KeysHandled . has ( keyCode ) ; } private static getStartValue ( parameters ) { const keyPressBackspaceOrDelete = parameters . keyPress === KEY_BACKSPACE || parameters . keyPress === KEY_DELETE ; if ( keyPressBackspaceOrDelete ) { return '' ; } if ( parameters . charPress ) { return parameters . charPress ; } return parameters . formatValue ( parameters . value ) ; } constructor ( ) { super ( '<div class="ag-wrapper ag-input-wrapper ag-text-field-input-wrapper ag-cell-editor-autocomplete-wrapper" style="padding: 0 !important;"><input class="ag-input-field-input ag-text-field-input ag-cell-editor-autocomplete-input" type="text"/></div>' ) ; this . eInput = this . getGui ( ) . querySelector ( 'input' ) as HTMLInputElement ; if ( this . currentItem ) { this . eInput . value = this . currentItem . label || ( this . currentItem . value as string ) ; } } public init ( parameters ) { this . stopEditing = parameters . stopEditing ; const defaultSettings = AutocompleteSelectCellEditor . getDefaultAutocompleteSettings ( parameters ) ; this . focusAfterAttached = parameters . cellStartedEdit ; this . eInput . placeholder = parameters . placeholder || '' ; this . eInput . value = AutocompleteSelectCellEditor . getStartValue ( parameters ) ; const autocompleteParameters = { ... defaultSettings , ... parameters . autocomplete } ; this . autocompleter = autocomplete < DataFormat > ( { input : this . eInput , render : ( item , currentValue ) => { return autocompleteParameters . render ( this , item , currentValue ) ; } , renderGroup : ( name , currentValue ) => { return autocompleteParameters . renderGroup ( this , name , currentValue ) ; } , className : autocompleteParameters . className , minLength : autocompleteParameters . minLength , emptyMsg : autocompleteParameters . emptyMsg , strict : autocompleteParameters . strict , autoselectfirst : autocompleteParameters . autoselectfirst , showOnFocus : autocompleteParameters . showOnFocus , onFreeTextSelect : ( item , input ) => { return autocompleteParameters . onFreeTextSelect ( this , item , input ) ; } , onSelect : ( item , input , event ) => { const result = autocompleteParameters . onSelect ( this , item , input ) ; if ( event instanceof KeyboardEvent || event instanceof event . view ! . document . defaultView ! . KeyboardEvent ) { this . handleTabEvent ( event ) ; } else { this . destroy ( ) ; } return result ; } , fetch : ( text , update , trigger ) => { return autocompleteParameters . fetch ( this , text , update , trigger ) ; } , debounceWaitMs : autocompleteParameters . debounceWaitMs , customize : ( input , inputRect , container , maxHeight ) => { return autocompleteParameters . customize ( this , input , inputRect , container , maxHeight ) ; } , } ) ; if ( parameters . required ) { this . required = true ; } if ( ! parameters . colDef . suppressKeyboardEvent ) { parameters . colDef . suppressKeyboardEvent = AutocompleteSelectCellEditor . suppressKeyboardEvent ; } } handleTabEvent ( event ) { const keyCode = event . which || event . keyCode || 0 ; if ( keyCode === KEY_TAB && this . gridOptionsWrapper ) { if ( event . shiftKey ) { this . gridOptionsWrapper . getApi ( ) ! . tabToPreviousCell ( ) ; } else { this . gridOptionsWrapper . getApi ( ) ! . tabToNextCell ( ) ; } } else { this . destroy ( ) ; } } afterGuiAttached ( ) { if ( ! this . focusAfterAttached ) { return ; } const { eInput } = this ; eInput . focus ( ) ; eInput . select ( ) ; const length = eInput . value ? eInput . value . length : 0 ; if ( length > 0 ) { eInput . setSelectionRange ( length , length ) ; } } focusIn ( ) { this . eInput . focus ( ) ; this . eInput . select ( ) ; } focusOut ( ) { this . eInput . blur ( ) ; this . autocompleter . destroy ( ) ; } destroy ( ) { this . focusOut ( ) ; if ( this . stopEditing ) { this . stopEditing ( ) ; } } getValue ( ) { return this . currentItem ; } isCancelAfterEnd ( ) { if ( this . required ) { return ! this . currentItem ; } return false ; } isCancelBeforeStart ( ) { return false ; } isPopup ( ) { return false ; } }