diff --git a/packages/semi-ui-vue/src/App.tsx b/packages/semi-ui-vue/src/App.tsx index a32b2784..93988bf9 100755 --- a/packages/semi-ui-vue/src/App.tsx +++ b/packages/semi-ui-vue/src/App.tsx @@ -160,6 +160,7 @@ import SelectDocsDemo from './components/select/__test__/SelectDocsDemo'; import HotKeysDemo2 from './components/hotKeys/__test__/HotKeysDemo2'; import HotKeysDocsDemo from './components/hotKeys/__test__/HotKeysDocsDemo'; import OptionDemo from './components/autoComplete/__test__/OptionDemo'; +import TransferDemoDocs from './components/transfer/__test__/TransferDemoDocs'; export interface ExampleProps { name?: string @@ -297,9 +298,10 @@ const App = defineComponent((props, {slots}) => { {/**/} {/**/} {/**/} + {/**/} - + {/**/} {/**/} {/**/} {/**/} diff --git a/packages/semi-ui-vue/src/components/_sortable/index.tsx b/packages/semi-ui-vue/src/components/_sortable/index.tsx new file mode 100644 index 00000000..bcc94ce6 --- /dev/null +++ b/packages/semi-ui-vue/src/components/_sortable/index.tsx @@ -0,0 +1,243 @@ +import cls from 'classnames'; +import * as PropTypes from '../PropTypes'; +import { isNull } from 'lodash'; +import { computed, defineComponent, h, PropType, ref, shallowRef, useSlots, VNode, watch } from 'vue'; +import { CombineProps, VueJsxNode } from '../interface'; +import { vuePropsMake } from '../PropTypes'; +import { UniqueIdentifier } from '@dnd-kit/abstract'; +import { DragDropProvider, type Events } from '@kousum/dnd-kit-vue'; +import { useSortable } from '@kousum/dnd-kit-vue/sortable'; +import { type CollisionDetector, pointerIntersection } from '@dnd-kit/collision'; + +const defaultPrefix = 'semi-sortable'; + +interface OnSortEndProps { + oldIndex: number; + newIndex: number +} +export type OnSortEnd = (event: Parameters[0]) => void; + +export interface RenderItemProps { + id?: string | number; + sortableHandle?: any; + [x: string]: any +} +export interface SortableProps { + collisionDetector?: CollisionDetector + onSortEnd?: OnSortEnd; + // Set drag and drop trigger conditions + modifiers?: any[]; + // the dragged items,The content in items cannot be the number 0 + items?: any[]; + // Function that renders the item that is allowed to be dragged + renderItem?: (props: RenderItemProps) => VueJsxNode; + // Whether to use a separate drag layer for items that move with the mouse + useDragOverlay?: boolean; + // A container for all elements that are allowed to be dragged + container?: any; + // Whether to change the size of the item being dragged + adjustScale?: boolean; + // prefix + prefix?: string; + // The className of the item that moves with the mouse during the drag + dragOverlayCls?: string +} + +interface SortableItemProps { + collisionDetector?: CollisionDetector + id: UniqueIdentifier; + index: number; + useDragOverlay?: boolean; + renderItem?: (props: RenderItemProps) => VueJsxNode; + prefix?: string; + // The className of the item that moves with the mouse during the drag + dragOverlayCls?: string +} + +function DefaultContainer(props) { + return
; +} + + +export const propTypesSortable: CombineProps = { + collisionDetector: PropTypes.func as PropType, + onSortEnd: PropTypes.func as PropType, + // Set drag and drop trigger conditions + modifiers: PropTypes.array as PropType, + // the dragged items,The content in items cannot be the number 0 + items: PropTypes.array, + // Function that renders the item that is allowed to be dragged + renderItem: PropTypes.func as PropType, + // Whether to use a separate drag layer for items that move with the mouse + useDragOverlay: PropTypes.bool, + // A container for all elements that are allowed to be dragged + container: PropTypes.any as PropType, + // Whether to change the size of the item being dragged + adjustScale: PropTypes.bool, + // prefix + prefix: PropTypes.string, + // The className of the item that moves with the mouse during the drag + dragOverlayCls: PropTypes.string, +}; +const defaultPropsSortable = { + useDragOverlay: true, + container: DefaultContainer, +} + +const vuePropsTypeSortable = vuePropsMake(propTypesSortable, defaultPropsSortable) + +const Sortable = defineComponent({ + props: { ...vuePropsTypeSortable }, + name: 'Sortable', + setup(props, { attrs }) { + const slots = useSlots(); + + const activeId = ref(null); + function setActiveId(v: UniqueIdentifier | null) { + activeId.value = v + } + // const sensors = useSensors( + // useSensor(MouseSensor), + // useSensor(TouchSensor), + // useSensor(KeyboardSensor, defaultKeyBoardOptions) + // ); + const getIndex = (id: UniqueIdentifier) => props.items.indexOf(id) + const activeIndex = computed(() => activeId ? getIndex(activeId.value) : -1) + + const onDragStart = (event: Parameters[0]) => { + const { source } = event.operation + if (!source) { return; } + setActiveId(source.id); + } + + const onDragEnd = (event: Parameters[0]) => { + setActiveId(null); + const { target } = event.operation + if (target) { + const overIndex = getIndex(target.id); + if (activeIndex.value !== overIndex) { + props.onSortEnd(event); + } + } + } + + const onDragCancel = () => { + setActiveId(null); + } + + + return () => { + const Container = props.container + return ( + { + // console.log('end', event.operation.source.id, event.operation.target.id,); + onDragEnd(event as any) + }} + onDragEnd={(event)=>{ + // console.log('end', event.operation.source.id, event.operation.target.id,); + }} + > + + {props.items.map((value, index) => ( + + ))} + + + ); + }; + }, +}); + + + +export const vuePropsType: CombineProps = { + collisionDetector: PropTypes.func as PropType, + id: { + type: [PropTypes.number, PropTypes.string], + required: true + }, + index: { + type: PropTypes.number, + required: true + }, + useDragOverlay: PropTypes.bool, + renderItem: PropTypes.func as PropType, + prefix: PropTypes.string, + // The className of the item that moves with the mouse during the drag + dragOverlayCls: PropTypes.string, +}; + + +const SortableItem = defineComponent({ + props: { ...vuePropsType }, + name: 'SortableItem', + setup(props, { attrs }) { + const slots = useSlots(); + const element = ref(null); + const handleRef = ref(null); + const id = shallowRef(props.id) + watch(()=>props.id, (value, oldValue)=>{ + if(value !== oldValue){ + id.value = oldValue; + } + }) + const index = shallowRef(props.index) + watch(()=>props.index, (value, oldValue)=>{ + if(value !== oldValue){ + index.value = oldValue; + } + }) + const {isDragSource, isDropTarget} = useSortable({ + id: id as any, + index: index as any, + element, + handle: handleRef, + collisionDetector: props.collisionDetector + }); + + const sortableHandle = (WrapperComponent: any) => { + // console.log('listeners', listeners); + // 保证给出的接口的一致性,使用 span 包一层,保证用户能够通过同样的方式使用 handler + // To ensure the consistency of the given interface + // use a span package layer to ensure that users can use the handler in the same way + // eslint-disable-next-line jsx-a11y/no-static-element-interactions + return () => ; + }; + + + return () => { + const itemCls = cls( + `${props.prefix}-sortable-item`, + { + // [`${props.prefix}-sortable-item-over`]: isDropTarget?.value, + // [`${props.prefix}-sortable-item-active`]: isDragSource?.value, + [props.dragOverlayCls]: isDragSource?.value, + } + ); + + return
+ {props.renderItem({ id: props.id, sortableHandle })} +
; + }; + }, +}); + +export { + Sortable, + SortableItem +} diff --git a/packages/semi-ui-vue/src/components/tagInput/index.tsx b/packages/semi-ui-vue/src/components/tagInput/index.tsx index e28600fb..b08cf93c 100644 --- a/packages/semi-ui-vue/src/components/tagInput/index.tsx +++ b/packages/semi-ui-vue/src/components/tagInput/index.tsx @@ -33,6 +33,7 @@ import { isSemiIcon } from '../_utils'; import SortableList from './SortableList'; import { type Events } from '@kousum/dnd-kit-vue'; import {move} from '@dnd-kit/helpers'; +import { RenderItemProps, Sortable } from '../_sortable'; export type Size = ArrayElement; export type RestTagsPopoverProps = PopoverProps; @@ -43,6 +44,11 @@ export type SortableItemFuncArg = { handleRef?: VNodeRef; attributes?: any; }; +function SortContainer(props:any, {slots}:any) { + return
+ {slots.default?.()} +
; +} export interface TagInputProps { className?: string; @@ -417,8 +423,19 @@ const Index = defineComponent({ ); } const getAllTags = () => { - const { size, disabled, renderTagItem, showContentTooltip, draggable } = props; - const { tagsArray, active } = state; + const { tagsArray } = state; + return tagsArray.map((value, index) => renderTag(value, index)); + } + + const renderTag = (value: any, index: number, sortableHandle?: any) => { + const { + size, + disabled, + renderTagItem, + showContentTooltip, + draggable, + } = props; + const { active } = state; const showIconHandler = active && draggable; const tagCls = cls(`${prefixCls}-wrapper-tag`, { [`${prefixCls}-wrapper-tag-size-${size}`]: size, @@ -431,63 +448,49 @@ const Index = defineComponent({ [`${prefixCls}-drag-item`]: showIconHandler, [`${prefixCls}-wrapper-tag-icon`]: showIconHandler, }); - // const DragHandle = SortableHandle(() => { - const elementKey = showIconHandler ? value : `${index}${value}`; - const onClose = () => { - !disabled && handleTagClose(index); - }; - if (isFunction(renderTagItem)) { - return (arg: SortableItemFuncArg) => { - return showIconHandler ? ( -
- - {renderTagItem(value, index, onClose)} -
- ) : ( - renderTagItem(value, index, onClose) - ); - }; - } else { - return (arg: SortableItemFuncArg) => { - return ( -
- - {/* Wrap a layer of div outside IconHandler and Value to ensure that the two are aligned */} - {showIconHandler && ( - - )} - - {value} - - -
- ); - }; - } - }); - }; + const DragHandle = sortableHandle && sortableHandle(() => ); + const elementKey = showIconHandler ? value : `${index}${value}`; + const onClose = () => { + !disabled && handleTagClose(index); + }; + if (isFunction(renderTagItem)) { + return (
+ {showIconHandler && sortableHandle ? : null} + {renderTagItem(value, index, onClose)} +
); + } else { + return ( + + {showIconHandler && sortableHandle ? : null} + + {value} + + + ); + } + } + + const renderSortTag = (props: RenderItemProps) => { + const { id: item, sortableHandle } = props; + const { tagsArray } = state; + const index = tagsArray.indexOf(item as string); + return renderTag(item, index, sortableHandle); + } - const onSortOver = (event: Parameters[0]) => { + const onSortEnd = (event: Parameters[0]) =>{ const tagsArray = state.tagsArray; const { active, over } = {active: event.operation.source, over: event.operation.target}; @@ -500,9 +503,6 @@ const Index = defineComponent({ const newIndex = tagsArray.indexOf(''+over.id); foundation.handleSortEnd({ oldIndex, newIndex }); } - }; - const onSortEnd = (event: Parameters[0]) =>{ - // adapter().setTagsArray([...newArr]); } function renderTags() { const { @@ -517,9 +517,8 @@ const Index = defineComponent({ const restTagsCls = cls(`${prefixCls}-wrapper-n`, { [`${prefixCls}-wrapper-n-disabled`]: disabled, }); - const allTagsFunc = getAllTags(); let restTags: Array = []; - const allTags = allTagsFunc.map((item) => item({})); + const allTags = getAllTags(); let tags: Array = [...allTags]; if ((!active || !expandRestTagsOnClick) && maxTagCount && maxTagCount < allTags.length) { tags = allTags.slice(0, maxTagCount); @@ -528,27 +527,20 @@ const Index = defineComponent({ const restTagsContent = +{tagsArray.length - maxTagCount}; - const sortableListItems = allTagsFunc.map((item, index) => ({ + + const sortableListItems = allTags.map((item, index) => ({ item: item, key: tagsArray[index], - id: tagsArray[index], })); - if (active && draggable && sortableListItems.length > 0) { - // helperClass:add styles to the helper(item being dragged) https://github.com/clauderic/react-sortable-hoc/issues/87 - // @ts-ignore skip SortableItem type check - return ( - - {sortableListItems} - - ); + return ; } return ( <> diff --git a/packages/semi-ui-vue/src/components/transfer/__test__/TransferDemoDocs.tsx b/packages/semi-ui-vue/src/components/transfer/__test__/TransferDemoDocs.tsx new file mode 100644 index 00000000..8a19be95 --- /dev/null +++ b/packages/semi-ui-vue/src/components/transfer/__test__/TransferDemoDocs.tsx @@ -0,0 +1,79 @@ + +import { IconHandle, IconClose } from '@kousum/semi-icons-vue'; +import { defineComponent } from 'vue'; +import Transfer from '../../transfer'; +import Avatar from '../../avatar'; +import { Checkbox } from '../../checkbox'; + +const Comp = defineComponent(() => { + const renderSourceItem = item => { + return ( +
+ { + item.onChange(); + }} + key={item.label} + checked={item.checked} + style={{ height: '52px', alignItems: 'center' }} + > + + {item.abbr} + +
+
{item.label}
+
{item.value}
+
+
+
+ ); + }; + + const renderSelectedItem = item => { + const { sortableHandle } = item; + const DragHandle = sortableHandle((arg:any) => ); + return ( +
+ + + {item.abbr} + +
+
{item.label}
+
{item.value}
+
+ +
+ ); + }; + + const customFilter = (sugInput, item) => { + return item.value.includes(sugInput) || item.label.includes(sugInput); + }; + + const data = [ + { label: '夏可漫', value: 'xiakeman@example.com', abbr: '夏', color: 'amber', area: 'US', key: 1 }, + { label: '申悦', value: 'shenyue@example.com', abbr: '申', color: 'indigo', area: 'UK', key: 2 }, + { label: '文嘉茂', value: 'wenjiamao@example.com', abbr: '文', color: 'cyan', area: 'HK', key: 3 }, + { label: '曲晨一', value: 'quchenyi@example.com', abbr: '曲', color: 'blue', area: 'India', key: 4 }, + { label: '曲晨二', value: 'quchener@example.com', abbr: '二', color: 'blue', area: 'India', key: 5 }, + { label: '曲晨三', value: 'quchensan@example.com', abbr: '三', color: 'blue', area: 'India', key: 6 }, + ]; + + return ()=>( + { + // console.log(values, items) + }} + /> + ); +}) +export default Comp; diff --git a/packages/semi-ui-vue/src/components/transfer/index.tsx b/packages/semi-ui-vue/src/components/transfer/index.tsx index d01b3eaa..0b292a51 100644 --- a/packages/semi-ui-vue/src/components/transfer/index.tsx +++ b/packages/semi-ui-vue/src/components/transfer/index.tsx @@ -38,6 +38,9 @@ import { CombineProps, VueJsxNode } from '../interface'; import SortableList from './SortableList'; import type { SortableItemFuncArg } from '../tagInput'; import type { Events } from '@kousum/dnd-kit-vue'; +import { RenderItemProps, Sortable } from '../_sortable'; +import { RestrictToVerticalAxis } from '@dnd-kit/abstract/modifiers'; +import { pointerIntersection } from '@dnd-kit/collision'; export interface DataItem extends BasicDataItem { label?: VueJsxNode; @@ -101,7 +104,7 @@ export interface SourcePanelProps { onSelect: (value: Array) => void; } -export type OnSortEnd = ({ oldIndex, newIndex }: OnSortEndProps) => void; +export type OnSortEnd = (event: Parameters[0]) => void; export interface SelectedPanelProps { /* Number of selected options */ @@ -180,7 +183,7 @@ export interface TransferProps { onDeselect?: (item: DataItem) => void; onSearch?: (sunInput: string) => void; renderSourceItem?: (item: RenderSourceItemProps) => VNode; - renderSelectedItem?: (item: RenderSelectedItemProps, arg: SortableItemFuncArg) => VueJsxNode; + renderSelectedItem?: (item: RenderSelectedItemProps) => VueJsxNode; renderSourcePanel?: (sourcePanelProps: SourcePanelProps) => VueJsxNode; renderSelectedPanel?: (selectedPanelProps: SelectedPanelProps) => VueJsxNode; renderSourceHeader?: (headProps: SourceHeaderProps) => VueJsxNode; @@ -352,7 +355,7 @@ const Transfer = defineComponent({ foundation.handleSelectOrRemove(item); } - function onSortEnd(event?: Parameters[0], callbackProps?: OnSortEndProps) { + function onSortEnd(event?: Parameters[0]) { if (event) { const { active, over } = {active: event.operation.source, over: event.operation.target}; const selectedItems = adapter.getSelected(); @@ -367,9 +370,6 @@ const Transfer = defineComponent({ foundation.handleSortEnd({ oldIndex, newIndex }); } } - if (callbackProps) { - foundation.handleSortEnd(callbackProps); - } } // function onSortEnd(callbackProps: OnSortEndProps) { // foundation.handleSortEnd(callbackProps); @@ -620,7 +620,7 @@ const Transfer = defineComponent({ ); } - function renderRightItem(item: ResolvedDataItem) { + function renderRightItem(item: ResolvedDataItem, sortableHandle?: any) { const { renderSelectedItem, draggable, type, showPath } = props; const onRemove = () => foundation.handleSelectOrRemove(item); const rightItemCls = cls({ @@ -633,40 +633,34 @@ const Transfer = defineComponent({ const label = shouldShowPath ? foundation._generatePath(item) : item.label; if (renderSelectedItem) { - return (arg: SortableItemFuncArg) => renderSelectedItem({ ...item, onRemove, sortableHandle: () => {} }, arg); + return renderSelectedItem({ ...item, onRemove, sortableHandle: sortableHandle }); } - return (arg: SortableItemFuncArg) => { - return ( - // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex -
- {draggable ? ( - - ) : null} -
{label}
- -
- ); - }; + const DragHandle = sortableHandle && sortableHandle(() => ( + + )); + return ( + // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex +
+ {draggable && sortableHandle ? : null} +
{label}
+ +
+ ); + } + const renderSortItem = (props: RenderItemProps): VueJsxNode => { + const { id, sortableHandle } = props; + const { selectedItems } = state; + const selectedData = [...selectedItems.values()]; + const item = selectedData.find(item => item.key === id); + return renderRightItem(item, sortableHandle); } - function renderEmpty(type: string, emptyText: VueJsxNode) { const emptyCls = cls({ [`${prefixCls}-empty`]: true, @@ -681,25 +675,38 @@ const Transfer = defineComponent({ } function renderRightSortableList(selectedData: Array) { - const sortableListItems = selectedData.map((item) => { - return { - ...item, - id: item.key, - node: renderRightItem(item), - }; - }); - - // helperClass:add styles to the helper(item being dragged) https://github.com/clauderic/react-sortable-hoc/issues/87 - // @ts-ignore skip SortableItem type check - const sortList = ( - - ); + const sortItems = selectedData.map(item => item.key); + const sortList = ; return sortList; + + + // const sortableListItems = selectedData.map((item) => { + // return { + // ...item, + // id: item.key, + // node: renderRightItem(item), + // }; + // }); + // + // // helperClass:add styles to the helper(item being dragged) https://github.com/clauderic/react-sortable-hoc/issues/87 + // // @ts-ignore skip SortableItem type check + // const sortList = ( + // + // ); + // return sortList; } function renderRight(locale: Locale['Transfer']) { @@ -713,7 +720,7 @@ const Transfer = defineComponent({ selectedData, onClear: () => foundation.handleClear(), onRemove: (item) => foundation.handleSelectOrRemove(item), - onSortEnd: (props) => onSortEnd(null, props), + onSortEnd: (props) => onSortEnd(props), }; if (renderSelectedPanel) { return renderSelectedPanel(renderProps); @@ -743,7 +750,7 @@ const Transfer = defineComponent({ const list = (
{selectedData.map((item) => { - return renderRightItem({ ...item })({}); + return renderRightItem({ ...item }); })}
);