UNPKG

@activecollab/components

Version:

ActiveCollab Components

394 lines (393 loc) • 13.5 kB
import _styled from "styled-components"; import React, { useCallback, useState, useMemo, useEffect, useRef } from "react"; import { Scrollbars } from "react-custom-scrollbars-2"; import { handleKeyboardMovement } from "./HandleKeyboard"; import { StyledAutocompleteBody, StyledAutocompleteNewItem, StyledAutocompleteScrollShadow } from "./Styles"; import highlightText from "../../hooks/useHighlightText"; import { Option } from "../Select/Option"; import { StyledOption } from "../Select/Option/Styles"; import { OptionGroup } from "../Select/OptionGroup"; export function isOptionGroup(item) { return item.options !== undefined; } export const Autocomplete = _ref => { let { type, options = [], inputEl, selected = [], emptyValue, noResultText, renderOption = option => option == null ? void 0 : option.name, defaultValue, sortDirection = "asc", handleChange, optionClassName, handleEmptyAction, disabledInternalSort, AutocompleteClassName, handleDefaultOptionChange, preselectDefaultValue, keepSameOptionsOrder = false, autoHeightMax = 340, clearInputOnSelect, mixedOptions = [] } = _ref; const itemRef = useRef(null); const listRef = useRef(null); const selectedOptions = useMemo(() => { if (Array.isArray(selected)) { return selected; } return [selected]; }, [selected]); const handleSort = useCallback(opts => { var _opts$; if (keepSameOptionsOrder) { return opts; } const isGrouped = Array.isArray((_opts$ = opts[0]) == null ? void 0 : _opts$.options); const sortOptions = (a, b) => { const aSelected = selectedOptions.includes(a.id); const bSelected = selectedOptions.includes(b.id); const aMixed = mixedOptions.includes(a.id); const bMixed = mixedOptions.includes(b.id); if (aSelected && !bSelected) { return -1; } if (!aSelected && bSelected) { return 1; } if (aMixed && !bMixed) { return -1; } if (!aMixed && bMixed) { return 1; } return 0; }; if (isGrouped) { return opts.map(group => { const sortedGroupOptions = group.options.sort(sortOptions); return { ...group, options: sortedGroupOptions }; }); } else { return opts.sort(sortOptions); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const sortList = useCallback(options => { var _options$; if (disabledInternalSort) { return handleSort([...options]); } const isGrouped = Array.isArray((_options$ = options[0]) == null ? void 0 : _options$.options); if (isGrouped) { const sortedOptions = options.map(group => { const sortedGroupOptions = group.options.sort((a, b) => sortDirection === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)); return { ...group, options: handleSort(sortedGroupOptions) }; }); return handleSort(sortedOptions); } else { const sortedOptions = options.sort((a, b) => sortDirection === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)); return handleSort(sortedOptions); } }, [sortDirection, disabledInternalSort, handleSort]); const sortedList = useMemo(() => sortList(options), [options, sortList]); const [hover, setHover] = useState({ item: undefined, by: undefined }); const [filter, setFilter] = useState(""); const handleEmpty = useCallback(e => { if (e && e.button !== 0) { return; } if (handleEmptyAction) { var _inputEl$current; inputEl == null || (_inputEl$current = inputEl.current) == null || _inputEl$current.focus(); setFilter(""); handleEmptyAction(filter); } }, [filter, handleEmptyAction, inputEl]); const onAddNewMouseEnter = useCallback(() => setHover({ item: "addNew", by: "mouse" }), []); const showAddNew = useMemo(() => { return !!(emptyValue && filter.trim() && options.every(option => { if (isOptionGroup(option)) { return option.options.every(v => v.name.toLowerCase() !== filter.trim().toLowerCase()); } return option.name.toLowerCase() !== filter.trim().toLowerCase(); })); }, [emptyValue, filter, options]); const renderAddNew = useMemo(() => { return /*#__PURE__*/React.createElement(StyledAutocompleteNewItem, { ref: hover.item === "addNew" ? itemRef : null, key: "emptyValue", hover: hover.item === "addNew", onMouseDown: handleEmpty, onMouseEnter: onAddNewMouseEnter }, emptyValue); }, [emptyValue, handleEmpty, hover, onAddNewMouseEnter]); const filterOptions = useCallback((options, filter) => { const trimmedFilter = filter.trim(); const isGrouped = options[0] && isOptionGroup(options[0]); if (isGrouped) { let hovered = false; return options.reduce((acc, groupedOption) => { const filteredOptions = groupedOption.options.filter(v => v.name.toLowerCase().includes(trimmedFilter.toLowerCase())); if (filteredOptions.length > 0) { if (!hovered && filter) { setHover({ item: filteredOptions[0].id, by: "keyboard" }); } hovered = true; return [...acc, { ...groupedOption, options: filteredOptions }]; } return [...acc]; }, []); } else { const filteredOptions = options.filter(v => v.name.toLowerCase().includes(trimmedFilter.toLowerCase())); if (filter && filteredOptions.length > 0) { setHover({ item: filteredOptions[0].id, by: "keyboard" }); } if (filteredOptions.length === 0 && emptyValue) { setHover({ item: "addNew", by: "keyboard" }); } return filteredOptions; } }, [emptyValue]); const list = useMemo(() => filterOptions(sortedList, filter), [filter, filterOptions, sortedList]); const showDefaultOption = useMemo(() => !!defaultValue && !filter, [defaultValue, filter]); const flatOptions = useMemo(() => { const options = list.reduce((acc, option) => { if (!isOptionGroup(option)) { return [...acc, option]; } return [...acc, ...option.options]; }, []); return filterOptions(options, filter); }, [filter, filterOptions, list]); const handleInputChange = useCallback(e => { if (e.target && !(e.key === "ArrowDown") && !(e.key === "ArrowUp") && !(e.key === "Enter")) { setFilter(e.target.value); } }, []); const handleHoverCallback = useCallback(e => { setHover({ item: e, by: "mouse" }); }, []); const toggleSelected = useCallback(id => { let result; if (id !== null) { if (type === "multiple") { if (selectedOptions.includes(id)) { result = selectedOptions.filter(_id => _id !== id); } else { result = [...selectedOptions, id]; } } else { result = id; } if (clearInputOnSelect && inputEl != null && inputEl.current) { inputEl.current.value = ""; inputEl.current.dispatchEvent(new Event("change", { bubbles: true })); } setFilter(""); } else { if (typeof handleDefaultOptionChange === "function") { handleDefaultOptionChange(); return; } } if (typeof handleChange === "function") { handleChange(result); } }, [clearInputOnSelect, handleChange, handleDefaultOptionChange, inputEl, selectedOptions, type]); const handleMouseEnter = useCallback(e => { if (e === undefined || e === null) { return setHover({ item: null, by: "mouse" }); } setHover({ item: e, by: "mouse" }); }, []); const handleClick = useCallback(e => { e.preventDefault(); toggleSelected(hover.item); }, [toggleSelected, hover]); const handleRenderOption = useCallback((item, index) => { if (isOptionGroup(item)) { return /*#__PURE__*/React.createElement(OptionGroup, { checked: selectedOptions, name: item.name, tooltip: item.tooltip, key: item.id, setHover: handleHoverCallback, id: item.id, hover: hover.item, options: item.options, renderOptions: handleRenderOption, type: type, onChange: handleChange, filter: filter, mixedOptions: mixedOptions }); } return /*#__PURE__*/React.createElement(Option, { name: item.name, ref: itemRef, key: index, tooltip: item.tooltip, onMouseEnter: handleMouseEnter, onClick: handleClick, id: item.id, hover: item.id === hover.item, className: optionClassName, renderOption: renderOption({ ...item, name: highlightText(item.name, filter) }, { id: "option_" + item.id, checked: selectedOptions && selectedOptions.includes(item.id), hover: hover.item === item.id, onChange: () => null }) }); }, [handleClick, handleMouseEnter, hover.item, optionClassName, renderOption, filter, selectedOptions, handleHoverCallback, type, handleChange, mixedOptions]); const handleOnMouseLeave = useCallback(() => { setHover({ item: undefined, by: "mouse" }); }, []); const handleScroll = useCallback((list, itemOffset) => { if (list.getClientHeight() + list.getScrollTop() < itemOffset + 40) { return list.scrollTop(itemOffset - list.getClientHeight() + 30); } if (list.getScrollTop() > itemOffset) { return list.scrollTop(itemOffset); } }, []); useEffect(() => { if (hover.by === "keyboard" && hover.item !== undefined) { const item = itemRef.current; const list = listRef.current; if (itemRef && item && list) { handleScroll(list, item.offsetTop); } } }, [handleScroll, hover]); const handleOnKeyDown = useCallback(e => { if (e.key === "Enter") { e.preventDefault(); if (hover.item === undefined && filter === "") { return; } if (hover.item === "addNew") { handleEmpty(null); return; } if (hover.item === null && handleDefaultOptionChange) { handleDefaultOptionChange(); return; } if (typeof hover.item !== "undefined" || hover.item !== null) { toggleSelected(hover.item); setFilter(""); } return; } setHover({ item: handleKeyboardMovement(e, hover.item, flatOptions, showAddNew, showDefaultOption), by: "keyboard" }); }, [filter, flatOptions, toggleSelected, handleDefaultOptionChange, handleEmpty, hover, showAddNew, showDefaultOption]); useEffect(() => { var _inputEl$current2; const listenerTarget = (_inputEl$current2 = inputEl == null ? void 0 : inputEl.current) != null ? _inputEl$current2 : document; listenerTarget.addEventListener("input", handleInputChange); listenerTarget.addEventListener("keydown", handleOnKeyDown); return () => { listenerTarget.removeEventListener("input", handleInputChange); listenerTarget.removeEventListener("keydown", handleOnKeyDown); }; }, [handleInputChange, handleOnKeyDown, inputEl]); const isDefaultOptionSelected = useCallback(() => { if (preselectDefaultValue) { return selectedOptions.length < 1 || selectedOptions[0] === ""; } else { return selectedOptions[0] === null || selectedOptions.length === flatOptions.length; } }, [flatOptions.length, preselectDefaultValue, selectedOptions]); const showNoResult = useMemo(() => noResultText && !showAddNew && list.length < 1 && (defaultValue && filter || !defaultValue), [noResultText, showAddNew, list.length, defaultValue, filter]); const renderNoResult = useMemo(() => /*#__PURE__*/React.createElement(_StyledStyledOption, null, noResultText), [noResultText]); return /*#__PURE__*/React.createElement(StyledAutocompleteScrollShadow, { className: AutocompleteClassName, $isHidden: !defaultValue && !emptyValue && !noResultText && list.length < 1 }, _ref2 => { let { onScroll } = _ref2; return /*#__PURE__*/React.createElement(StyledAutocompleteBody, { key: "body", onMouseLeave: handleOnMouseLeave }, /*#__PURE__*/React.createElement(Scrollbars, { ref: listRef, key: "scrollBar", autoHeight: true, autoHeightMax: autoHeightMax, onScroll: onScroll }, defaultValue && !filter && /*#__PURE__*/React.createElement(Option, { name: defaultValue, ref: itemRef, hover: hover.item === null, onMouseEnter: handleMouseEnter, onClick: e => { e.preventDefault(); toggleSelected(null); }, renderOption: renderOption({ name: defaultValue, id: null }, { checked: isDefaultOptionSelected(), hover: hover.item === null, onChange: () => null }) }), list.map((item, index) => handleRenderOption(item, index)), showNoResult && renderNoResult, showAddNew && renderAddNew)); }); }; Autocomplete.displayName = "Autocomplete"; var _StyledStyledOption = _styled(StyledOption).withConfig({ displayName: "Autocomplete___StyledStyledOption", componentId: "sc-9x4q7e-0" })(["cursor:auto"]); //# sourceMappingURL=Autocomplete.js.map