mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: autocomplete: dnd support
This commit is contained in:
1 parent
217facc710
commit
422c078b0c
2 files changed
+56
-11
No files matched your search
@@ -3,8 +3,10 @@
|
|||||||
import React, {
|
import React, {
|
||||||
forwardRef, useState, useRef, useImperativeHandle, useEffect,
|
forwardRef, useState, useRef, useImperativeHandle, useEffect,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { debounce } from 'lodash';
|
import { debounce, uniqueId } from 'lodash';
|
||||||
import Icon from 'vj/components/react/IconComponent';
|
import Icon from 'vj/components/react/IconComponent';
|
||||||
|
|
||||||
export interface AutoCompleteProps<Item> {
|
export interface AutoCompleteProps<Item> {
|
||||||
@@ -25,6 +27,7 @@ export interface AutoCompleteProps<Item> {
|
|||||||
itemKey?: (item: Item) => string;
|
itemKey?: (item: Item) => string;
|
||||||
onChange?: (value: string) => any;
|
onChange?: (value: string) => any;
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
|
draggable?: boolean;
|
||||||
selectedKeys?: string[];
|
selectedKeys?: string[];
|
||||||
allowEmptyQuery?: boolean;
|
allowEmptyQuery?: boolean;
|
||||||
freeSolo?: boolean;
|
freeSolo?: boolean;
|
||||||
@@ -51,6 +54,30 @@ export interface AutoCompleteHandle<Item> {
|
|||||||
|
|
||||||
const superCache = {};
|
const superCache = {};
|
||||||
|
|
||||||
|
function DraggableSelection({
|
||||||
|
type, id, move, children, ...props
|
||||||
|
}) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const [isDragging, drag] = useDrag<string, any, boolean>(() => ({
|
||||||
|
type,
|
||||||
|
item: id,
|
||||||
|
collect: (m) => m.isDragging(),
|
||||||
|
}));
|
||||||
|
const [, drop] = useDrop({
|
||||||
|
accept: type,
|
||||||
|
hover: (item: string) => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
move(item, id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
drag(drop(ref));
|
||||||
|
return (
|
||||||
|
<div ref={ref} {...props} style={{ opacity: isDragging ? 0.2 : 1 }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-arrow-callback
|
// eslint-disable-next-line prefer-arrow-callback
|
||||||
const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, ref: React.Ref<AutoCompleteHandle<T>>) {
|
const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, ref: React.Ref<AutoCompleteHandle<T>>) {
|
||||||
const width = props.width ?? '100%';
|
const width = props.width ?? '100%';
|
||||||
@@ -64,6 +91,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
const itemKey = props.itemKey ?? itemText;
|
const itemKey = props.itemKey ?? itemText;
|
||||||
const onChange = props.onChange ?? (() => { });
|
const onChange = props.onChange ?? (() => { });
|
||||||
const multi = props.multi ?? false;
|
const multi = props.multi ?? false;
|
||||||
|
const draggable = props.draggable ?? props.multi;
|
||||||
const allowEmptyQuery = props.allowEmptyQuery ?? false;
|
const allowEmptyQuery = props.allowEmptyQuery ?? false;
|
||||||
const freeSolo = props.freeSolo ?? false;
|
const freeSolo = props.freeSolo ?? false;
|
||||||
const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i);
|
const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i);
|
||||||
@@ -74,6 +102,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
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);
|
||||||
|
const [draggableId] = useState(uniqueId());
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>();
|
const inputRef = useRef<HTMLInputElement>();
|
||||||
const listRef = useRef<HTMLUListElement>();
|
const listRef = useRef<HTMLUListElement>();
|
||||||
@@ -224,28 +253,34 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
},
|
},
|
||||||
}), [selected, selectedKeys, inputRef, multi]);
|
}), [selected, selectedKeys, inputRef, multi]);
|
||||||
|
|
||||||
|
const move = (dragId: string, hoverId: string) => {
|
||||||
|
if (dragId === hoverId || !draggable) return;
|
||||||
|
const dragIndex = selectedKeys.indexOf(dragId);
|
||||||
|
const hoverIndex = selectedKeys.indexOf(hoverId);
|
||||||
|
setSelectedKeys((s) => {
|
||||||
|
const a = [...s];
|
||||||
|
a.splice(dragIndex, 1);
|
||||||
|
a.splice(hoverIndex, 0, s[dragIndex]);
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'inline-block', width: '100%' }}>
|
<div style={{ display: 'inline-block', width: '100%' }}>
|
||||||
<div
|
<div
|
||||||
className={focused ? 'autocomplete-wrapper focused' : 'autocomplete-wrapper'}
|
className={focused ? 'autocomplete-wrapper focused' : 'autocomplete-wrapper'}
|
||||||
style={{ width, height }}
|
style={{ width, height }}
|
||||||
>
|
>
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
{multi && selectedKeys.map((key) => {
|
{multi && selectedKeys.map((key) => {
|
||||||
const item = valueCache[key];
|
const item = valueCache[key];
|
||||||
return (
|
return (
|
||||||
<div className="autocomplete-tag" key={item ? key : `draft-${key}`}>
|
<DraggableSelection type={draggableId} id={key} move={move} className="autocomplete-tag" key={item ? key : `draft-${key}`}>
|
||||||
<div>{item ? itemText(item) : key}</div>
|
<div>{item ? itemText(item) : key}</div>
|
||||||
<Icon name="close" onClick={() => toggleItem(item, key)} />
|
<Icon name="close" onClick={() => toggleItem(item, key)} />
|
||||||
</div>
|
</DraggableSelection>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{disabled && (
|
|
||||||
<input
|
|
||||||
disabled
|
|
||||||
autoComplete="off"
|
|
||||||
value={disabledHint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -262,7 +297,15 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
|
|||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleInputKeyDown}
|
||||||
defaultValue={multi ? '' : selectedKeys.join(',')}
|
defaultValue={multi ? '' : selectedKeys.join(',')}
|
||||||
/>
|
/>
|
||||||
|
</DndProvider>
|
||||||
</div>
|
</div>
|
||||||
|
{disabled && (
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
autoComplete="off"
|
||||||
|
value={disabledHint}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{focused && itemList.length > 0 && (
|
{focused && itemList.length > 0 && (
|
||||||
<ul ref={listRef} className="autocomplete-list" style={listStyle} onMouseDown={(e) => e.preventDefault()}>
|
<ul ref={listRef} className="autocomplete-list" style={listStyle} onMouseDown={(e) => e.preventDefault()}>
|
||||||
{itemList.map((item, idx) => (
|
{itemList.map((item, idx) => (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.34.35",
|
"version": "4.34.36",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
@@ -77,6 +77,8 @@
|
|||||||
"queue-microtask": "^1.2.3",
|
"queue-microtask": "^1.2.3",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
"react-dnd": "^16.0.0",
|
||||||
|
"react-dnd-html5-backend": "^16.0.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-redux": "^7.2.8",
|
"react-redux": "^7.2.8",
|
||||||
"react-split-pane": "^0.1.92",
|
"react-split-pane": "^0.1.92",
|
||||||
|
|||||||
Reference in new issue
Block a user