packageDocumentation module import "./AutoSuggest.scss" ; import * as React from "react" ; import * as ReactAutosuggest from "react-autosuggest" ; import { Logger } from "@itwin/core-bentley" ; import { SpecialKey } from "@itwin/appui-abstract" ; import { CommonProps } from "../utils/Props" ; import { UiCore } from "../UiCore" ; public export interface AutoSuggestData { /** Value of [[AutoSuggest]] option. */ value : string ; /** Label of [[AutoSuggest]] option. This is shown in the dropdown. */ label : string ; } public export type GetAutoSuggestDataFunc = ( value : string ) => AutoSuggestData [ ] ; public export type AsyncGetAutoSuggestDataFunc = ( value : string ) => Promise < AutoSuggestData [ ] > ; public export interface AutoSuggestProps extends React . InputHTMLAttributes < HTMLInputElement > , CommonProps { /** Optional input value override. */ value ? : string ; /** Options for dropdown. */ options ? : AutoSuggestData [ ] | GetAutoSuggestDataFunc ; /** Asynchronously calculate suggestions for any given input value. */ getSuggestions ? : AsyncGetAutoSuggestDataFunc ; /** Gets a label associated with a given value */ getLabel ? : ( value : string | undefined ) => string ; /** Handler for when suggested selected. */ onSuggestionSelected : ( selected : AutoSuggestData ) => void ; /** Handler for Enter key. */ onPressEnter ? : ( e : React . KeyboardEvent < HTMLInputElement > ) => void ; /** Handler for Escape key. */ onPressEscape ? : ( e : React . KeyboardEvent < HTMLInputElement > ) => void ; /** Handler for Tab key. */ onPressTab ? : ( e : React . KeyboardEvent < HTMLInputElement > ) => void ; /** Handler for input receiving focus. */ onInputFocus ? : ( e : React . FocusEvent < HTMLInputElement > ) => void ; /** Called every time you need to clear suggestions. */ onSuggestionsClearRequested ? : ( ) => void ; /** Indicates whether to set focus to the input element */ setFocus ? : boolean ; internal renderInputComponent ? : any ; internal renderSuggestionsContainer ? : ReactAutosuggest . RenderSuggestionsContainer ; internal alwaysRenderSuggestions ? : boolean ; } internal interface AutoSuggestState { inputValue : string ; suggestions : AutoSuggestData [ ] ; } public export class AutoSuggest extends React . PureComponent < AutoSuggestProps , AutoSuggestState > { private _isMounted = false ; constructor ( props ) { super ( props ) ; this . state = { inputValue : this . getLabel ( props . value ) , suggestions : [ ] } ; } internal public override componentDidMount ( ) { this . _isMounted = true ; } internal public override componentWillUnmount ( ) { this . _isMounted = false ; } public override componentDidUpdate ( prevProps ) { if ( this . props . value !== prevProps . value || this . props . options !== prevProps . options ) { this . setState ( ( _prevState , props ) => ( { inputValue : this . getLabel ( props . value ) } ) ) ; } } private _onChange = ( e ) => { let newValue = "" ; if ( e . target . tagName === "LI" && e . target . textContent ) newValue = e . target . textContent ; else if ( e . target . value ) newValue = e . target . value ; this . setState ( { inputValue : newValue } ) ; } ; private _onFocus = ( e ) => { if ( this . props . onInputFocus ) this . props . onInputFocus ( e ) ; } ; /** Autosuggest will call this function every time you need to update suggestions. */ private _onSuggestionsFetchRequested = async ( request ) : Promise < void > => { const value = request . value ; const suggestions = await this . _getSuggestions ( value ) ; if ( this . _isMounted ) this . setState ( { suggestions } ) ; } ; /** Autosuggest will call this function every time you need to clear suggestions. */ private _onSuggestionsClearRequested = ( ) => { this . setState ( { suggestions : [ ] } ) ; this . props . onSuggestionsClearRequested && this . props . onSuggestionsClearRequested ( ) ; } ; private _onSuggestionSelected = ( _event , data ) : void => { this . props . onSuggestionSelected ( data . suggestion ) ; } ; /** Teach Autosuggest how to calculate suggestions for any given input value. */ private _getSuggestions = async ( value ) : Promise < AutoSuggestData [ ] > => { if ( typeof this . props . options === "function" ) return Promise . resolve ( this . props . options ( value ) ) ; if ( this . props . getSuggestions ) return this . props . getSuggestions ( value ) ; if ( this . props . options === undefined ) { Logger . logError ( UiCore . loggerCategory ( this ) , `props.options or props.getSuggestions should be provided` ) ; return Promise . resolve ( [ ] ) ; } const inputValue = value . trim ( ) . toLowerCase ( ) ; const inputLength = inputValue . length ; return Promise . resolve ( inputLength === 0 ? [ ] : this . props . options . filter ( ( data ) => { return data . label . toLowerCase ( ) . includes ( inputValue ) || data . value . toLowerCase ( ) . includes ( inputValue ) ; } ) ) ; } ; /** When suggestion is clicked, Autosuggest needs to populate the input based on the clicked suggestion.  */ private _getSuggestionValue = ( suggestion ) => suggestion . label ; /** Render each suggestion. */ private _renderSuggestion = ( suggestion ) => ( < span > { suggestion . label } < /span>); private getLabel ( value ) { let label = "" ; if ( this . props . getLabel ) { label = this . props . getLabel ( value ) ; } else if ( this . props . options instanceof Array ) { const entry = this . props . options . find ( ( data ) => data . value === value ) ; if ( entry ) label = entry . label ; } else { Logger . logError ( UiCore . loggerCategory ( this ) , `props.getLabel should be provided when props.options is a function` ) ; } return label ; } private _handleKeyDown = ( e ) => { switch ( e . key ) { case SpecialKey . Enter : if ( this . props . onPressEnter ) this . props . onPressEnter ( e ) ; break ; case SpecialKey . Escape : if ( this . props . onPressEscape ) this . props . onPressEscape ( e ) ; break ; case SpecialKey . Tab : if ( this . props . onPressTab ) this . props . onPressTab ( e ) ; break ; } } ; private _theme = { container : "uicore-autosuggest__container" , containerOpen : "uicore-autosuggest__container--open" , input : "uicore-autosuggest__input" , inputOpen : "uicore-autosuggest__input--open" , inputFocused : "uicore-autosuggest__input--focused" , suggestionsContainer : "uicore-autosuggest__suggestions-container" , suggestionsContainerOpen : "uicore-autosuggest__suggestions-container--open" , suggestionsList : "uicore-autosuggest__suggestions-list" , suggestion : "uicore-autosuggest__suggestion" , suggestionFirst : "uicore-autosuggest__suggestion--first" , suggestionHighlighted : "uicore-autosuggest__suggestion--highlighted" , sectionContainer : "uicore-autosuggest__section-container" , sectionContainerFirst : "uicore-autosuggest__section-container--first" , sectionTitle : "uicore-autosuggest__section-title" } ; public override render ( ) { const { inputValue , suggestions } = this . state ; const { value , onChange , placeholder , options , onSuggestionSelected , setFocus , alwaysRenderSuggestions , onPressEnter , onPressEscape , onPressTab , onInputFocus , getLabel , getSuggestions , renderInputComponent , renderSuggestionsContainer , onSuggestionsClearRequested , ... props } = this . props ; const inputPlaceholder = ( ! inputValue ) ? placeholder : undefined ; const inputProps = { ... props , value : inputValue , onChange : this . _onChange , onFocus : this . _onFocus , placeholder : inputPlaceholder , autoFocus : setFocus } ; return ( < div className = { this . props . className } style = { this . props . style } onKeyDown = { this . _handleKeyDown } > < ReactAutosuggest theme = { this . _theme } suggestions = { suggestions } onSuggestionsFetchRequested = { this . _onSuggestionsFetchRequested } onSuggestionsClearRequested = { this . _onSuggestionsClearRequested } getSuggestionValue = { this . _getSuggestionValue } renderSuggestion = { this . _renderSuggestion } inputProps = { inputProps } onSuggestionSelected = { this . _onSuggestionSelected } alwaysRenderSuggestions = { alwaysRenderSuggestions } renderInputComponent = { renderInputComponent } renderSuggestionsContainer = { renderSuggestionsContainer } / > < /div>); } }