import classNames from "classnames" ; import * as React from "react" ; import { AbstractPureComponent2 , DISPLAYNAME_PREFIX , InputGroupProps2 , InputGroup , IPopoverProps , IRef , Keys , Popover , PopoverInteractionKind , Position , refHandler , setRef , } from "@blueprintjs/core" ; import { Classes , IListItemsProps } from "../../common" ; import { IQueryListRendererProps , QueryList } from "../query-list/queryList" ; export type SuggestProps < T > = ISuggestProps < T > ; deprecated export interface ISuggestProps < T > extends IListItemsProps < T > { default closeOnSelect ? : boolean ; /** Whether the input field should be disabled. */ disabled ? : boolean ; /**
     * Whether the component should take up the full width of its container.
     * This overrides `popoverProps.fill` and `inputProps.fill`.
     */ fill ? : boolean ; /**
     * Props to spread to the query `InputGroup`. To control this input, use
     * `query` and `onQueryChange` instead of `inputProps.value` and
     * `inputProps.onChange`.
     */ inputProps ? : InputGroupProps2 ; /** Custom renderer to transform an item into a string for the input value. */ inputValueRenderer : ( item : T ) => string ; /**
     * The uncontrolled default selected item.
     * This prop is ignored if `selectedItem` is used to control the state.
     */ defaultSelectedItem ? : T ; /**
     * The currently selected item, or `null` to indicate that no item is selected.
     * If omitted or `undefined`, this prop will be uncontrolled (managed by the component's state).
     * Use `onItemSelect` to listen for updates.
     */ selectedItem ? : T | null ; default openOnKeyDown ? : boolean ; /** Props to spread to `Popover`. Note that `content` cannot be changed. */ popoverProps ? : Partial < IPopoverProps > & object ; default resetOnClose ? : boolean ; } export interface ISuggestState < T > { isOpen : boolean ; selectedItem : T | null ; } export class Suggest < T > extends AbstractPureComponent2 < SuggestProps < T > , ISuggestState < T > > { public static displayName = `${ DISPLAYNAME_PREFIX }.Suggest` ; public static defaultProps : Partial < SuggestProps < any > > = { closeOnSelect : true , fill : false , openOnKeyDown : false , resetOnClose : false , } ; public static ofType < U > ( ) { return Suggest as new ( props ) => Suggest < U > ; } public state : ISuggestState < T > = { isOpen : ( this . props . popoverProps != null && this . props . popoverProps . isOpen ) || false , selectedItem : this . getInitialSelectedItem ( ) , } ; private TypedQueryList = QueryList . ofType < T > ( ) ; public inputElement : HTMLInputElement | null = null ; private queryList : QueryList < T > | null = null ; private handleInputRef : IRef < HTMLInputElement > = refHandler ( this , "inputElement" , this . props . inputProps ?. inputRef ) ; private handleQueryListRef = ( ref ) => ( this . queryList = ref ) ; public render ( ) { const { disabled , inputProps , popoverProps , ... restProps } = this . props ; return ( < this . TypedQueryList { ... restProps } initialActiveItem = { this . props . selectedItem ?? undefined } onItemSelect = { this . handleItemSelect } ref = { this . handleQueryListRef } renderer = { this . renderQueryList } / > ) ; } public componentDidUpdate ( prevProps , prevState ) { if ( prevProps . inputProps ?. inputRef !== this . props . inputProps ?. inputRef ) { setRef ( prevProps . inputProps ?. inputRef , null ) ; this . handleInputRef = refHandler ( this , "inputElement" , this . props . inputProps ?. inputRef ) ; setRef ( this . props . inputProps ?. inputRef , this . inputElement ) ; } if ( this . props . selectedItem !== undefined && this . props . selectedItem !== this . state . selectedItem ) { this . setState ( { selectedItem : this . props . selectedItem } ) ; } if ( this . state . isOpen === false && prevState . isOpen === true ) { const timeout = this . props . popoverProps ?. transitionDuration ?? Popover . defaultProps . transitionDuration ; setTimeout ( ( ) => this . maybeResetActiveItemToSelectedItem ( ) , timeout ) ; } if ( this . state . isOpen && ! prevState . isOpen && this . queryList != null ) { this . queryList . scrollActiveItemIntoView ( ) ; } } private renderQueryList = ( listProps ) => { const { fill , inputProps = { } , popoverProps = { } } = this . props ; const { isOpen , selectedItem } = this . state ; const { handleKeyDown , handleKeyUp } = listProps ; const { autoComplete = "off" , placeholder = "Search..." } = inputProps ; const selectedItemText = selectedItem ? this . props . inputValueRenderer ( selectedItem ) : "" ; const inputPlaceholder = isOpen && selectedItemText ? selectedItemText : placeholder ; const inputValue = isOpen ? listProps . query : selectedItemText || ( this . props . resetOnClose ? "" : listProps . query ) ; if ( fill ) { popoverProps . fill = true ; inputProps . fill = true ; } return ( < Popover autoFocus = { false } enforceFocus = { false } isOpen = { isOpen } position = { Position . BOTTOM_LEFT } { ... popoverProps } className = { classNames ( listProps . className , popoverProps . className ) } interactionKind = { PopoverInteractionKind . CLICK } onInteraction = { this . handlePopoverInteraction } popoverClassName = { classNames ( Classes . SELECT_POPOVER , popoverProps . popoverClassName ) } onOpening = { this . handlePopoverOpening } onOpened = { this . handlePopoverOpened } > < InputGroup autoComplete = { autoComplete } disabled = { this . props . disabled } { ... inputProps } inputRef = { this . handleInputRef } onChange = { listProps . handleQueryChange } onFocus = { this . handleInputFocus } onKeyDown = { this . getTargetKeyDownHandler ( handleKeyDown ) } onKeyUp = { this . getTargetKeyUpHandler ( handleKeyUp ) } placeholder = { inputPlaceholder } value = { inputValue } / > < div onKeyDown = { handleKeyDown } onKeyUp = { handleKeyUp } > { listProps . itemList } < /div> { } < /Popover>); } ; private selectText = ( ) => { this . requestAnimationFrame ( ( ) => { this . inputElement ?. setSelectionRange ( 0 , this . inputElement . value . length ) ; } ) ; } ; private handleInputFocus = ( event ) => { this . selectText ( ) ; if ( ! this . props . openOnKeyDown ) { this . setState ( { isOpen : true } ) ; } this . props . inputProps ?. onFocus ?. ( event ) ; } ; private handleItemSelect = ( item , event ? ) => { let nextOpenState ; if ( ! this . props . closeOnSelect ) { this . inputElement ?. focus ( ) ; this . selectText ( ) ; nextOpenState = true ; } else { this . inputElement ?. blur ( ) ; nextOpenState = false ; } if ( this . props . selectedItem === undefined ) { this . setState ( { isOpen : nextOpenState , selectedItem : item , } ) ; } else { this . setState ( { isOpen : nextOpenState } ) ; } this . props . onItemSelect ?. ( item , event ) ; } ; private getInitialSelectedItem ( ) { if ( this . props . selectedItem !== undefined ) { return this . props . selectedItem ; } else if ( this . props . defaultSelectedItem !== undefined ) { return this . props . defaultSelectedItem ; } else { return null ; } } private handlePopoverInteraction = ( nextOpenState , event ? ) => this . requestAnimationFrame ( ( ) => { const isInputFocused = this . inputElement === document . activeElement ; if ( this . inputElement != null && ! isInputFocused ) { this . setState ( { isOpen : false } ) ; } this . props . popoverProps ?. onInteraction ?. ( nextOpenState , event ) ; } ) ; private handlePopoverOpening = ( node ) => { if ( this . props . resetOnClose && this . queryList ) { this . queryList . setQuery ( "" , true ) ; } this . props . popoverProps ?. onOpening ?. ( node ) ; } ; private handlePopoverOpened = ( node ) => { if ( this . queryList != null ) { this . queryList . scrollActiveItemIntoView ( ) ; } this . props . popoverProps ?. onOpened ?. ( node ) ; } ; private getTargetKeyDownHandler = ( handleQueryListKeyDown ) => { return ( evt ) => { const { which } = evt ; if ( which === Keys . ESCAPE || which === Keys . TAB ) { this . inputElement ?. blur ( ) ; this . setState ( { isOpen : false } ) ; } else if ( this . props . openOnKeyDown && which !== Keys . BACKSPACE && which !== Keys . ARROW_LEFT && which !== Keys . ARROW_RIGHT ) { this . setState ( { isOpen : true } ) ; } if ( this . state . isOpen ) { handleQueryListKeyDown ?. ( evt ) ; } this . props . inputProps ?. onKeyDown ?. ( evt ) ; } ; } ; private getTargetKeyUpHandler = ( handleQueryListKeyUp ) => { return ( evt ) => { if ( this . state . isOpen ) { handleQueryListKeyUp ?. ( evt ) ; } this . props . inputProps ?. onKeyUp ?. ( evt ) ; } ; } ; private maybeResetActiveItemToSelectedItem ( ) { const shouldResetActiveItemToSelectedItem = this . props . activeItem === undefined && this . state . selectedItem !== null && ! this . props . resetOnSelect ; if ( this . queryList !== null && shouldResetActiveItemToSelectedItem ) { this . queryList . setActiveItem ( this . props . selectedItem ?? this . state . selectedItem ) ; } } }