import React , { useState , useEffect , Fragment , useCallback } from 'react' ; import { isEmpty , isEqual , mapValues , omitBy , pick } from 'lodash' ; import { EuiButtonIcon , EuiFlexGroup , EuiFlexItem , EuiSpacer , htmlIdGenerator , EuiButtonEmpty , } from '@elastic/eui' ; import { FormattedMessage } from '@osd/i18n/react' ; export interface InputListConfig { defaultValue : InputItemModel ; validateClass : new ( value ) => { toString ( ) : string ; } ; getModelValue ( item ? ) : InputItemModel ; getRemoveBtnAriaLabel ( model ) : string ; onChangeFn ( model ) : InputObject ; hasInvalidValuesFn ( model ) : boolean ; renderInputRow ( model , index , onChangeFn ) : React . ReactNode ; modelNames : string | string [ ] ; } interface InputModelBase { id : string ; } export type InputObject = object ; export interface InputItem { model : string ; value : string ; isInvalid : boolean ; } interface InputItemModel { [ model ] : InputItem ; } export type InputModel = InputModelBase & InputItemModel ; interface InputListProps { config : InputListConfig ; list : InputObject [ ] ; onChange ( list ) : void ; setValidity ( isValid ) : void ; } const generateId = htmlIdGenerator ( ) ; const validateValue = ( inputValue , InputObject ) => { const result = { model : inputValue || '' , isInvalid : false , } ; if ( ! inputValue ) { result . isInvalid = false ; return result ; } try { result . model = new InputObject ( inputValue ) . toString ( ) ; result . isInvalid = false ; return result ; } catch ( e ) { result . isInvalid = true ; return result ; } } ; function InputList ( { config , list , onChange , setValidity } ) { const { defaultValue , getModelValue , modelNames , onChangeFn , validateClass } = config ; const [ models , setModels ] = useState ( ( ) => list . map ( ( item ) => ( { id : generateId ( ) , ... getModelValue ( item ) , } as InputModel ) ) ) ; const hasInvalidValues = models . some ( config . hasInvalidValuesFn ) ; const updateValues = useCallback ( ( modelList ) => { setModels ( modelList ) ; onChange ( modelList . map ( onChangeFn ) ) ; } , [ onChangeFn , onChange ] ) ; const onChangeValue = useCallback ( ( index , value , modelName ) => { const { model , isInvalid } = validateValue ( value , validateClass ) ; updateValues ( models . map ( ( range , arrayIndex ) => arrayIndex === index ? { ... range , [ modelName ] : { value , model , isInvalid , } , } : range ) ) ; } , [ models , updateValues , validateClass ] ) ; const onDelete = useCallback ( ( id ) => updateValues ( models . filter ( ( model ) => model . id !== id ) ) , [ models , updateValues ] ) ; const onAdd = useCallback ( ( ) => updateValues ( [ ... models , { id : generateId ( ) , ... getModelValue ( ) , } as InputModel , ] ) , [ getModelValue , models , updateValues ] ) ; useEffect ( ( ) => { if ( ! list . length ) { updateValues ( [ { id : generateId ( ) , ... defaultValue , } as InputModel , ] ) ; } } , [ defaultValue , list . length , updateValues ] ) ; useEffect ( ( ) => { setValidity ( ! hasInvalidValues ) ; } , [ hasInvalidValues , setValidity ] ) ; useEffect ( ( ) => { if ( list . length !== models . length || list . some ( ( item , index ) => { const model = mapValues ( pick ( models [ index ] , modelNames ) , 'model' ) ; return ! isEqual ( item , omitBy ( model , isEmpty ) ) ; } ) ) { setModels ( list . map ( ( item ) => ( { id : generateId ( ) , ... getModelValue ( item ) , } as InputModel ) ) ) ; } } , [ getModelValue , list , modelNames , models ] ) ; return ( < > { models . map ( ( item , index ) => ( < Fragment key = { item . id } > < EuiFlexGroup gutterSize = "xs" alignItems = "center" responsive = { false } > { config . renderInputRow ( item , index , onChangeValue ) } < EuiFlexItem grow = { false } > < EuiButtonIcon aria - label = { config . getRemoveBtnAriaLabel ( item ) } title = { config . getRemoveBtnAriaLabel ( item ) } disabled = { models . length === 1 } color = "danger" iconType = "trash" onClick = { ( ) => onDelete ( item . id ) } / > < /EuiFlexItem> < /EuiFlexGroup> < EuiSpacer size = "xs" / > < /Fragment>))} < EuiSpacer size = "s" / > < EuiFlexItem > < EuiButtonEmpty iconType = "plusInCircleFilled" onClick = { onAdd } size = "xs" > < FormattedMessage id = "visDefaultEditor.controls.ipRanges.addRangeButtonLabel" defaultMessage = "Add range" / > < /EuiButtonEmpty> < /EuiFlexItem> < />); } export { InputList } ;