ui: enhanced auto-complete

pull/340/head
undefined 3 years ago
parent c383c40477
commit 2b56444239

@ -38,6 +38,7 @@ export interface AutoCompleteHandle<Item> {
setSelectedItems: (items: Item[]) => void;
getQuery: () => string;
setQuery: (query: string) => void;
setSelectedKeys: (keys: string[]) => void;
triggerQuery: () => any;
closeList: () => void;
/** returns comma seperated value text */
@ -69,7 +70,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
const [focused, setFocused] = useState(false); // is focused
const [selected, setSelected] = useState([]); // selected items
const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys); // keys of selected items
const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys || []); // keys of selected items
const [itemList, setItemList] = useState([]); // items list
const [currentItem, setCurrentItem] = useState(null); // index of current item (in item list)
const [rerender, setRerender] = useState(false);
@ -81,16 +82,6 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
const queryCache = props.cacheKey ? superCache[props.cacheKey].query : useRef({}).current;
const valueCache = props.cacheKey ? superCache[props.cacheKey].value : useRef({}).current;
useEffect(() => {
const ids = [];
for (const key of selectedKeys) if (!valueCache[key]) ids.push(key);
if (!ids.length) return;
Promise.resolve(props.fetchItems(ids)).then((items) => {
for (const item of items) valueCache[itemKey(item)] = item;
setRerender(!rerender);
});
}, [selectedKeys]);
const queryList = async (query) => {
if (!query && !allowEmptyQuery) {
setItemList([]);
@ -104,22 +95,25 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
};
const dispatchChange = () => {
const query = inputRef.current?.value;
const val = [...selectedKeys, query].filter((i) => i).join(',');
onChange(val);
if (!multi) onChange(inputRef.current?.value);
else onChange(selectedKeys.join(','));
};
useEffect(() => {
dispatchChange();
const ids = [];
for (const key of selectedKeys) if (!valueCache[key]) ids.push(key);
if (!ids.length) return;
Promise.resolve(props.fetchItems(ids)).then((items) => {
for (const item of items) valueCache[itemKey(item)] = item;
setRerender(!rerender);
});
}, [selectedKeys, multi]);
const handleInputChange = debounce(async (e) => {
const { target } = e;
const { value } = target;
queryList(value);
}, 300);
const handleInputChange = debounce((e?) => queryList(e ? e.target.value : ''), 300);
const toggleItem = (item: T, key = itemKey(item)) => {
const shouldKeepOpen = allowEmptyQuery && inputRef.current.value === '' && !multi;
if (multi) {
const idx = selectedKeys.indexOf(key);
if (idx !== -1) {
@ -145,8 +139,10 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
inputRef.current.value = key;
dispatchChange();
}
if (!shouldKeepOpen) {
setItemList([]);
setCurrentItem(null);
}
};
const handleInputKeyDown = (e) => {
@ -169,7 +165,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
}
if (key === 'Backspace') {
if (target.value.length > 0) return;
if (selected.length > 0) {
if (selectedKeys.length > 0) {
setSelected((s) => s.slice(0, -1));
setSelectedKeys((s) => s.slice(0, -1));
}
@ -209,6 +205,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
setQuery: (query) => {
if (inputRef.current) inputRef.current.value = query;
},
setSelectedKeys: (keys) => setSelectedKeys(keys),
triggerQuery: () => queryList(inputRef.current?.value),
closeList: () => {
setItemList([]);
@ -257,7 +254,10 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
dispatchChange();
handleInputChange(e);
}}
onFocus={() => setFocused(true)}
onFocus={() => {
if (allowEmptyQuery) handleInputChange();
setFocused(true);
}}
onBlur={() => setFocused(false)}
onKeyDown={handleInputKeyDown}
defaultValue={multi ? '' : selectedKeys.join(',')}

@ -2,13 +2,23 @@ import React from 'react';
import PropTypes from 'prop-types';
import AutoComplete from './AutoComplete';
type DefaultProps = {
_id: string;
name: string;
} & string;
const CustomSelectAutoComplete = React.forwardRef<any, any>((props, ref) => (
<AutoComplete<any>
<AutoComplete<DefaultProps>
ref={ref as any}
queryItems={(query) => props.data.filter((i) => (i.name ? i.name.match(query) : i.match(query)))}
fetchItems={(keys) => props.data.filter((i) => (i._id ? keys.includes(i._id) : keys.includes(i)))}
queryItems={(query) => props.data.filter((i) => (i.name
? i.name.toLowerCase().match(query.toLowerCase())
: i.toString().toLowerCase().match(query.toLowerCase())
))}
itemText={(item) => `${item.name || item}`}
itemKey={(item) => `${item._id?.toString() || item.name || item}`}
renderItem={(item) => `${item.name || item}`}
allowEmptyQuery
{...props}
/>
));
@ -34,7 +44,6 @@ CustomSelectAutoComplete.defaultProps = {
listStyle: {},
multi: false,
selectedKeys: [],
allowEmptyQuery: false,
freeSolo: false,
freeSoloConverter: (input) => input,
};

@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
"version": "4.34.33",
"version": "4.34.34",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0",
"main": "hydro.js",

@ -4,3 +4,4 @@ declare let UiContext: any;
declare let Hydro: any;
// eslint-disable-next-line camelcase
declare let node_modules: any;
declare let LANGS: Record<string, import('@hydrooj/utils/lib/lang').LangConfig>;

Loading…
Cancel
Save