@activecollab/components
Version:
ActiveCollab Components
394 lines (393 loc) • 13.5 kB
JavaScript
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