refactor: replace react-drag-listview with @dnd-kit/sortable (#660)
* refactor: replace react-drag-listview with @dnd-kit/sortable * fix: components are different every time * fix: incorrect border line * fix: when dragging is enabled for the first time, dragging is invalid * fix: the items property of SortableContext must be an array of strings * fix: onRowDragEnd may be non-exists * fix: incorrect row key * feat: wrap SortableContext only when dragging is enabled * fix: improve logic
This commit is contained in:
parent
49a4ab4818
commit
c697ef85a6
@ -4,11 +4,12 @@ import { ArrayField } from '@formily/core';
|
|||||||
import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||||
import { default as classNames, default as cls } from 'classnames';
|
import { default as classNames, default as cls } from 'classnames';
|
||||||
import React from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import ReactDragListView from 'react-drag-listview';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DndContext } from '../..';
|
import { DndContext } from '../..';
|
||||||
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
|
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
|
||||||
|
import { SortableContext, useSortable } from '@dnd-kit/sortable';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
const isColumnComponent = (schema: Schema) => {
|
const isColumnComponent = (schema: Schema) => {
|
||||||
return schema['x-component']?.endsWith('.Column') > -1;
|
return schema['x-component']?.endsWith('.Column') > -1;
|
||||||
@ -67,64 +68,35 @@ const useTableColumns = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const components = {
|
|
||||||
header: {
|
|
||||||
wrapper: (props) => {
|
|
||||||
return (
|
|
||||||
<DndContext>
|
|
||||||
<thead {...props} />
|
|
||||||
</DndContext>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cell: (props) => {
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
{...props}
|
|
||||||
className={cls(
|
|
||||||
props.className,
|
|
||||||
css`
|
|
||||||
max-width: 300px;
|
|
||||||
white-space: nowrap;
|
|
||||||
&:hover .general-schema-designer {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
wrapper: (props) => {
|
|
||||||
return (
|
|
||||||
<DndContext>
|
|
||||||
<tbody {...props} />
|
|
||||||
</DndContext>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
row: (props) => {
|
|
||||||
return <tr {...props} />;
|
|
||||||
},
|
|
||||||
cell: (props) => (
|
|
||||||
<td
|
|
||||||
{...props}
|
|
||||||
className={classNames(
|
|
||||||
props.className,
|
|
||||||
css`
|
|
||||||
max-width: 300px;
|
|
||||||
white-space: nowrap;
|
|
||||||
.nb-read-pretty-input-number {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const SortHandle = () => {
|
const topActiveClass = css`
|
||||||
return <MenuOutlined className={'drag-handle'} style={{ cursor: 'grab' }} />;
|
& > td {
|
||||||
|
border-top: 2px solid rgba(241, 139, 98, 0.6) !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const bottomActiveClass = css`
|
||||||
|
& > td {
|
||||||
|
border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const SortableRow = (props) => {
|
||||||
|
const id = props['data-row-key']?.toString()
|
||||||
|
const { setNodeRef, isOver, active, over } = useSortable({
|
||||||
|
id
|
||||||
|
})
|
||||||
|
|
||||||
|
const className = (active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable.index ?? -1) ? topActiveClass : bottomActiveClass
|
||||||
|
|
||||||
|
return <tr ref={active?.id !== id ? setNodeRef : null} {...props} className={classNames({ [className]: active && isOver })} />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const SortHandle = (props) => {
|
||||||
|
const { listeners } = useSortable({
|
||||||
|
id: props.id
|
||||||
|
})
|
||||||
|
return <MenuOutlined {...listeners} style={{ cursor: 'grab' }} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TableIndex = (props) => {
|
const TableIndex = (props) => {
|
||||||
@ -160,13 +132,97 @@ export const Table: any = observer((props: any) => {
|
|||||||
const {
|
const {
|
||||||
dragSort = false,
|
dragSort = false,
|
||||||
showIndex = true,
|
showIndex = true,
|
||||||
onRowDragEnd,
|
|
||||||
onRowSelectionChange,
|
onRowSelectionChange,
|
||||||
onChange: onTableChange,
|
onChange: onTableChange,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
|
rowKey,
|
||||||
...others
|
...others
|
||||||
} = { ...others1, ...others2 } as any;
|
} = { ...others1, ...others2 } as any;
|
||||||
|
const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {}))
|
||||||
const paginationProps = usePaginationProps(pagination1, pagination2);
|
const paginationProps = usePaginationProps(pagination1, pagination2);
|
||||||
|
|
||||||
|
const components = useMemo(() => {
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
wrapper: (props) => {
|
||||||
|
return (
|
||||||
|
<DndContext>
|
||||||
|
<thead {...props} />
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: (props) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
{...props}
|
||||||
|
className={cls(
|
||||||
|
props.className,
|
||||||
|
css`
|
||||||
|
max-width: 300px;
|
||||||
|
white-space: nowrap;
|
||||||
|
&:hover .general-schema-designer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
wrapper: (props) => {
|
||||||
|
return (
|
||||||
|
<DndContext onDragEnd={(e) => {
|
||||||
|
if (!e.active || !e.over) {
|
||||||
|
console.warn('move cancel')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromIndex = e.active?.data.current?.sortable?.index
|
||||||
|
const toIndex = e.over?.data.current?.sortable?.index
|
||||||
|
const from = field.value[fromIndex];
|
||||||
|
const to = field.value[toIndex];
|
||||||
|
field.move(fromIndex, toIndex);
|
||||||
|
onRowDragEnd({ fromIndex, toIndex, from, to });
|
||||||
|
}}>
|
||||||
|
<tbody {...props} />
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
row: (props) => {
|
||||||
|
return <SortableRow {...props}></SortableRow>
|
||||||
|
},
|
||||||
|
cell: (props) => (
|
||||||
|
<td
|
||||||
|
{...props}
|
||||||
|
className={classNames(
|
||||||
|
props.className,
|
||||||
|
css`
|
||||||
|
max-width: 300px;
|
||||||
|
white-space: nowrap;
|
||||||
|
.nb-read-pretty-input-number {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [field, onRowDragEnd, dragSort])
|
||||||
|
|
||||||
|
const defaultRowKey = (record: any) => {
|
||||||
|
return field.value?.indexOf?.(record);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowKey = (record: any) => {
|
||||||
|
if (typeof rowKey === 'string') {
|
||||||
|
return record[rowKey]?.toString();
|
||||||
|
} else {
|
||||||
|
return (rowKey ?? defaultRowKey)(record)?.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const restProps = {
|
const restProps = {
|
||||||
rowSelection: rowSelection
|
rowSelection: rowSelection
|
||||||
? {
|
? {
|
||||||
@ -226,7 +282,7 @@ export const Table: any = observer((props: any) => {
|
|||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{dragSort && <SortHandle />}
|
{dragSort && <SortHandle id={getRowKey(record)} />}
|
||||||
{showIndex && <TableIndex index={index} />}
|
{showIndex && <TableIndex index={index} />}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -252,9 +308,16 @@ export const Table: any = observer((props: any) => {
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
const defaultRowKey = (record: any) => {
|
|
||||||
return field.value?.indexOf?.(record);
|
const SortableWrapper = useCallback<React.FC>(({ children }) => {
|
||||||
};
|
return dragSort ? React.createElement(SortableContext, {
|
||||||
|
items: field.value.map(getRowKey),
|
||||||
|
children: children,
|
||||||
|
}) : React.createElement(React.Fragment, {
|
||||||
|
children
|
||||||
|
})
|
||||||
|
}, [field, dragSort])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={css`
|
className={css`
|
||||||
@ -264,20 +327,9 @@ export const Table: any = observer((props: any) => {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<ReactDragListView
|
<SortableWrapper>
|
||||||
handleSelector={'.drag-handle'}
|
|
||||||
onDragEnd={async (fromIndex, toIndex) => {
|
|
||||||
const from = field.value[fromIndex];
|
|
||||||
const to = field.value[toIndex];
|
|
||||||
field.move(fromIndex, toIndex);
|
|
||||||
onRowDragEnd({ fromIndex, toIndex, from, to });
|
|
||||||
}}
|
|
||||||
lineClassName={css`
|
|
||||||
border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important;
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<AntdTable
|
<AntdTable
|
||||||
rowKey={defaultRowKey}
|
rowKey={rowKey ?? defaultRowKey}
|
||||||
{...others}
|
{...others}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
pagination={paginationProps}
|
pagination={paginationProps}
|
||||||
@ -290,7 +342,7 @@ export const Table: any = observer((props: any) => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={field?.value?.slice?.()}
|
dataSource={field?.value?.slice?.()}
|
||||||
/>
|
/>
|
||||||
</ReactDragListView>
|
</SortableWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user