import * as React from "react"; import { usePrevious } from "../../../hooks/usePrevious"; import { ClassHelpers } from "../../../utilities/classHelpers"; import { KeyCodes } from "../../../utilities/keyCodes"; import { utils } from "../../../utilities/utils"; import { Icon } from "../../display/icon"; import { Button } from "../../interaction/button"; import { Col, Grid, Row } from "../../layout/grid"; import { DataValidationMessage, DataValidationMode, getEventTargetAs } from "../formCore"; import { ValidationLabel } from "../validationWrapper"; import { IAutoCompleteOption, IAutoCompleteProps } from "./autoCompleteOptionHooks"; type MessageComponent = React.ReactNode | ((value: string) => React.ReactNode) export const AutoCompleteSingleInput: React.FunctionComponent> = props => { const { filter, onFilterChange, options, value, onValueChange, className, visibleItems, hasGoButton, disabled, placeholder, canClear, isSearching, noResultsMessage, goButtonContent } = props const autocompleteSelectList = React.useRef() const input = React.useRef() const root = React.useRef() const [open, setOpen] = React.useState(false) const [showOnTop, setShowOnTop] = React.useState(false) const [topOffset, setTopOffset] = React.useState(-35) const [selectedIndex, setSelectedIndex] = React.useState(0) const [offsetIndex, setOffsetIndex] = React.useState(0) const itemHeight = 52 const previousOpen = usePrevious(open) React.useEffect(() => { if (previousOpen !== open) { if (open) { if (input.current) { input.current.focus() } } } }, [open]) function focusInput(e: React.MouseEvent) { if ((!e) || (!open && !getEventTargetAs(e).classList.contains("clear-selected"))) { setOpen(true) setShowOnTop(shouldShowOnTop()) document.addEventListener("click", handleEvent, false); } } function handleSelection(option: IAutoCompleteOption) { setOpen(false) onFilterChange("") setOffsetIndex(0) if (onValueChange) { onValueChange(option); } document.removeEventListener("click", handleEvent, false); } function shouldShowOnTop(): boolean { const height = (itemHeight * 3) + 50; if (!root.current) { return } const inputRect = root.current.getBoundingClientRect(); const remainingSpace = window.innerHeight - inputRect.bottom; let onTop = false; if (remainingSpace < height) { onTop = true; } else { onTop = false; } const offset = -inputRect.height; setTopOffset(onTop ? offset : 0); return onTop; } function handleEvent(e: Event) { if (root.current && root.current.contains(e.target as Node)) { return; } setOpen(false) onFilterChange("") document.removeEventListener("click", handleEvent, false); } function checkKey(e: React.KeyboardEvent) { if (e.keyCode === KeyCodes.escape) { setOpen(false) onFilterChange("") } if (e.keyCode === KeyCodes.downArrow && options.length !== 0) { if (!autocompleteSelectList.current) { return } const offsetIdx = Math.min((visibleItems || 3) - 1, offsetIndex + 1); const selectedIdx = Math.min(selectedIndex + 1, options.length - 1); setOffsetIndex(offsetIdx); if (offsetIdx >= 2) { autocompleteSelectList.current.scrollTop = (selectedIdx - 2) * itemHeight; } const selectedItem = options[selectedIdx] setSelectedIndex(selectedIdx) onFilterChange(selectedItem.name) e.preventDefault(); return false; } if (e.keyCode === KeyCodes.upArrow && options.length !== 0) { if (!autocompleteSelectList.current) { return } const offsetIdx = Math.max(offsetIndex - 1, 0); const selectedIdx = Math.max(selectedIndex - 1, 0); setOffsetIndex(offsetIdx); if (offsetIdx === 0) { autocompleteSelectList.current.scrollTop = (selectedIdx) * itemHeight; } const selectedItem = options[selectedIdx] setSelectedIndex(selectedIdx) onFilterChange(selectedItem.name) e.preventDefault(); return false; } if (e.keyCode === KeyCodes.enter && options.length !== 0) { const selectedValue = options[selectedIndex]; handleSelection(selectedValue); e.preventDefault(); return false; } } function getNoResults(search: string, message: MessageComponent): React.ReactNode { if (!message) { return "No results..." } if (utils.object.isFunction(message)) { return message(search) } return message; } function checkToFilter(query: string) { if (onFilterChange) { onFilterChange(query); } } function buttonClick() { if (options.length !== 0) { const selectedValue = options[selectedIndex]; if (selectedValue) { handleSelection(selectedValue); } } } const validationMessage = DataValidationMessage.get(props) const validationMode = DataValidationMode.get(props) const classes = ClassHelpers.classNames( "armstrong-input", "autocomplete-select", className, { "has-go-button": hasGoButton, "disabled": disabled, "show-validation": (validationMode !== "none" && validationMessage), "text-input-icon-left": props.leftIcon !== undefined }, ); return ( focusInput(e)} className={classes}> {props.leftIcon && } {!open && {utils.object.isNullOrUndefined(value) ?
 
{placeholder}
:
{value && value.name}
} {value && canClear && { onValueChange(null); onFilterChange(""); setOpen(false); }}> }
} {open &&
checkKey(e)} onChange={e => checkToFilter(getEventTargetAs(e).value)} placeholder={placeholder} /> {isSearching && }
{options && options.map((o, i) =>
handleSelection(o)}>{o.prefixElement}{o.name}
)} {options.length === 0 && filter &&
{getNoResults(filter, noResultsMessage)}
}
} {hasGoButton && }
} />
) } AutoCompleteSingleInput.defaultProps = { placeholder: "start typing to filter results...", }