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 { 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 interface IAutoCompleteOption { // id: number | string; // name: string; // data?: any; // className?: string; // style?: React.CSSProperties; // prefixElement?: React.ReactNode; // } export const AutoCompleteMultiInput: React.FunctionComponent> = props => { const { filter, onFilterChange, options, value, onValueChange, visibleItems, className, hasGoButton, disabled, placeholder, canClear, isSearching, noResultsMessage } = 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) { handleFocus(); } else if (!open && !getEventTargetAs(e).classList.contains("clear-selected")) { handleFocus(); } } 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 * 2; setTopOffset(onTop ? offset : inputRect.height); return onTop; } function handleFocus() { setOpen(true) setShowOnTop(shouldShowOnTop()) document.addEventListener("click", handleEvent, false); } function handleEvent(e: Event) { if ((e.target as HTMLDivElement).classList.contains("multi-select-item-part")) { return } if (root.current && root.current.contains(e.target as Node)) { return; } setOpen(false) onFilterChange("") document.removeEventListener("click", handleEvent, false); } function handleSelection(selected: IAutoCompleteOption) { if (onValueChange) { if (utils.array.some(value, ddo => ddo.id === selected.id)) { onValueChange(utils.array.reject(value, ddo => ddo.id === selected.id)); } else { onValueChange([...value, selected]) } } if (input.current) { input.current.focus() } } 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 checkToFilter(query: string) { if (onFilterChange) { onFilterChange(query); } } function getNoResults(search: string, message: MessageComponent): React.ReactNode { if (!message) { return "No results..." } if (utils.object.isFunction(message)) { return message(search) } return message; } const validationMessage = DataValidationMessage.get(props) const validationMode = DataValidationMode.get(props) const classes = ClassHelpers.classNames( "armstrong-input", "autocomplete-select", className, { "has-multiple-options": value.length !== 0, "has-go-button": hasGoButton, "disabled": disabled, "show-validation": (validationMode !== "none" && validationMessage), }, ); return ( focusInput(e)} className={classes}> {value &&
{value.map(ddo =>
handleSelection(ddo)} >{ddo.name}
)}
} {value.length === 0 &&
 
{!open && placeholder}
} {value.length !== 0 && canClear && { onValueChange([]); onFilterChange(""); setOpen(false); }}> }
{open &&
checkKey(e)} onChange={e => checkToFilter(getEventTargetAs(e).value)} placeholder={placeholder} /> {isSearching && }
{options && options.map((o, i) =>
ddo.id === o.id)) ? " in-selected-list" : ""}`} onClick={() => handleSelection(o)}>{o.prefixElement}{o.name}
)} {options.length === 0 && filter &&
{getNoResults(filter, noResultsMessage)}
}
}
} />
) } AutoCompleteMultiInput.defaultProps = { placeholder: "start typing to filter results...", }