mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: enhanced auto-complete
This commit is contained in:
1 parent
c383c40477
commit
2b56444239
4 files changed
+37
-27
No files matched your search
@@ -38,6 +38,7 @@ export interface AutoCompleteHandle<Item> {
|
|||||||
setSelectedItems: (items: Item[]) => void;
|
setSelectedItems: (items: Item[]) => void;
|
||||||
getQuery: () => string;
|
getQuery: () => string;
|
||||||
setQuery: (query: string) => void;
|
setQuery: (query: string) => void;
|
||||||
|
setSelectedKeys: (keys: string[]) => void;
|
||||||
triggerQuery: () => any;
|
triggerQuery: () => any;
|
||||||
closeList: () => void;
|
closeList: () => void;
|
||||||
/** returns comma seperated value text */
|
/** 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 [focused, setFocused] = useState(false); // is focused
|
||||||
const [selected, setSelected] = useState([]); // selected items
|
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 [itemList, setItemList] = useState([]); // items list
|
||||||
const [currentItem, setCurrentItem] = useState(null); // index of current item (in item list)
|
const [currentItem, setCurrentItem] = useState(null); // index of current item (in item list)
|
||||||
const [rerender, setRerender] = useState(false);
|
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 queryCache = props.cacheKey ? superCache[props.cacheKey].query : useRef({}).current;
|
||||||
const valueCache = props.cacheKey ? superCache[props.cacheKey].value : 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) => {
|
const queryList = async (query) => {
|
||||||
if (!query && !allowEmptyQuery) {
|
if (!query && !allowEmptyQuery) {
|
||||||
setItemList([]);
|
setItemList([]);
|
||||||
@@ -104,22 +95,25 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dispatchChange = () => {
|
const dispatchChange = () => {
|
||||||
const query = inputRef.current?.value;
|
if (!multi) onChange(inputRef.current?.value);
|
||||||
const val = [...selectedKeys, query].filter((i) => i).join(',');
|
else onChange(selectedKeys.join(','));
|
||||||
onChange(val);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatchChange();
|
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]);
|
}, [selectedKeys, multi]);
|
||||||
|
|
||||||
const handleInputChange = debounce(async (e) => {
|
const handleInputChange = debounce((e?) => queryList(e ? e.target.value : ''), 300);
|
||||||
const { target } = e;
|
|
||||||
const { value } = target;
|
|
||||||
queryList(value);
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
const toggleItem = (item: T, key = itemKey(item)) => {
|
const toggleItem = (item: T, key = itemKey(item)) => {
|
||||||
|
const shouldKeepOpen = allowEmptyQuery && inputRef.current.value === '' && !multi;
|
||||||
if (multi) {
|
if (multi) {
|
||||||
const idx = selectedKeys.indexOf(key);
|
const idx = selectedKeys.indexOf(key);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
@@ -145,8 +139,10 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
inputRef.current.value = key;
|
inputRef.current.value = key;
|
||||||
dispatchChange();
|
dispatchChange();
|
||||||
}
|
}
|
||||||
setItemList([]);
|
if (!shouldKeepOpen) {
|
||||||
setCurrentItem(null);
|
setItemList([]);
|
||||||
|
setCurrentItem(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputKeyDown = (e) => {
|
const handleInputKeyDown = (e) => {
|
||||||
@@ -169,7 +165,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
}
|
}
|
||||||
if (key === 'Backspace') {
|
if (key === 'Backspace') {
|
||||||
if (target.value.length > 0) return;
|
if (target.value.length > 0) return;
|
||||||
if (selected.length > 0) {
|
if (selectedKeys.length > 0) {
|
||||||
setSelected((s) => s.slice(0, -1));
|
setSelected((s) => s.slice(0, -1));
|
||||||
setSelectedKeys((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) => {
|
setQuery: (query) => {
|
||||||
if (inputRef.current) inputRef.current.value = query;
|
if (inputRef.current) inputRef.current.value = query;
|
||||||
},
|
},
|
||||||
|
setSelectedKeys: (keys) => setSelectedKeys(keys),
|
||||||
triggerQuery: () => queryList(inputRef.current?.value),
|
triggerQuery: () => queryList(inputRef.current?.value),
|
||||||
closeList: () => {
|
closeList: () => {
|
||||||
setItemList([]);
|
setItemList([]);
|
||||||
@@ -257,7 +254,10 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
dispatchChange();
|
dispatchChange();
|
||||||
handleInputChange(e);
|
handleInputChange(e);
|
||||||
}}
|
}}
|
||||||
onFocus={() => setFocused(true)}
|
onFocus={() => {
|
||||||
|
if (allowEmptyQuery) handleInputChange();
|
||||||
|
setFocused(true);
|
||||||
|
}}
|
||||||
onBlur={() => setFocused(false)}
|
onBlur={() => setFocused(false)}
|
||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleInputKeyDown}
|
||||||
defaultValue={multi ? '' : selectedKeys.join(',')}
|
defaultValue={multi ? '' : selectedKeys.join(',')}
|
||||||
|
|||||||
+12
-3
@@ -2,13 +2,23 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import AutoComplete from './AutoComplete';
|
import AutoComplete from './AutoComplete';
|
||||||
|
|
||||||
|
type DefaultProps = {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
} & string;
|
||||||
|
|
||||||
const CustomSelectAutoComplete = React.forwardRef<any, any>((props, ref) => (
|
const CustomSelectAutoComplete = React.forwardRef<any, any>((props, ref) => (
|
||||||
<AutoComplete<any>
|
<AutoComplete<DefaultProps>
|
||||||
ref={ref as any}
|
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}`}
|
itemText={(item) => `${item.name || item}`}
|
||||||
itemKey={(item) => `${item._id?.toString() || item.name || item}`}
|
itemKey={(item) => `${item._id?.toString() || item.name || item}`}
|
||||||
renderItem={(item) => `${item.name || item}`}
|
renderItem={(item) => `${item.name || item}`}
|
||||||
|
allowEmptyQuery
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@@ -34,7 +44,6 @@ CustomSelectAutoComplete.defaultProps = {
|
|||||||
listStyle: {},
|
listStyle: {},
|
||||||
multi: false,
|
multi: false,
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
allowEmptyQuery: false,
|
|
||||||
freeSolo: false,
|
freeSolo: false,
|
||||||
freeSoloConverter: (input) => input,
|
freeSoloConverter: (input) => input,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.34.33",
|
"version": "4.34.34",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ declare let UiContext: any;
|
|||||||
declare let Hydro: any;
|
declare let Hydro: any;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
declare let node_modules: any;
|
declare let node_modules: any;
|
||||||
|
declare let LANGS: Record<string, import('@hydrooj/utils/lib/lang').LangConfig>;
|
||||||
Reference in new issue
Block a user