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:
Dunqing 2022-07-25 19:34:29 +08:00 committed by GitHub
parent 49a4ab4818
commit c697ef85a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,11 +4,12 @@ import { ArrayField } from '@formily/core';
import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { Table as AntdTable, TableColumnProps } from 'antd';
import { default as classNames, default as cls } from 'classnames';
import React from 'react';
import ReactDragListView from 'react-drag-listview';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { DndContext } from '../..';
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
import { SortableContext, useSortable } from '@dnd-kit/sortable';
import { useMemoizedFn } from 'ahooks';
const isColumnComponent = (schema: Schema) => {
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 = () => {
return <MenuOutlined className={'drag-handle'} style={{ cursor: 'grab' }} />;
const topActiveClass = css`
& > 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) => {
@ -154,43 +126,127 @@ const usePaginationProps = (pagination1, pagination2) => {
export const Table: any = observer((props: any) => {
const field = useField<ArrayField>();
const columns = useTableColumns();
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
const { pagination: pagination2, ...others2 } = useProps?.() || {};
const {
const columns = useTableColumns();
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
const { pagination: pagination2, ...others2 } = useProps?.() || {};
const {
dragSort = false,
showIndex = true,
onRowDragEnd,
onRowSelectionChange,
onChange: onTableChange,
rowSelection,
rowKey,
...others
} = { ...others1, ...others2 } as any;
const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {}))
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 = {
rowSelection: rowSelection
? {
type: 'checkbox',
selectedRowKeys: field?.data?.selectedRowKeys || [],
onChange(selectedRowKeys: any[], selectedRows: any[]) {
field.data = field.data || {};
field.data.selectedRowKeys = selectedRowKeys;
onRowSelectionChange?.(selectedRowKeys, selectedRows);
},
renderCell: (checked, record, index, originNode) => {
if (!dragSort && !showIndex) {
return originNode;
}
const current = props?.pagination?.current;
const pageSize = props?.pagination?.pageSize || 20;
if (current) {
index = index + (current - 1) * pageSize;
}
return (
<div
className={classNames(
checked ? 'checked' : null,
css`
type: 'checkbox',
selectedRowKeys: field?.data?.selectedRowKeys || [],
onChange(selectedRowKeys: any[], selectedRows: any[]) {
field.data = field.data || {};
field.data.selectedRowKeys = selectedRowKeys;
onRowSelectionChange?.(selectedRowKeys, selectedRows);
},
renderCell: (checked, record, index, originNode) => {
if (!dragSort && !showIndex) {
return originNode;
}
const current = props?.pagination?.current;
const pageSize = props?.pagination?.pageSize || 20;
if (current) {
index = index + (current - 1) * pageSize;
}
return (
<div
className={classNames(
checked ? 'checked' : null,
css`
position: relative;
display: flex;
align-items: center;
@ -213,27 +269,27 @@ export const Table: any = observer((props: any) => {
}
}
`,
)}
>
<div
className={classNames(
checked ? 'checked' : null,
css`
)}
>
<div
className={classNames(
checked ? 'checked' : null,
css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
`,
)}
>
{dragSort && <SortHandle />}
{showIndex && <TableIndex index={index} />}
</div>
<div
className={classNames(
'nb-origin-node',
checked ? 'checked' : null,
css`
)}
>
{dragSort && <SortHandle id={getRowKey(record)} />}
{showIndex && <TableIndex index={index} />}
</div>
<div
className={classNames(
'nb-origin-node',
checked ? 'checked' : null,
css`
position: absolute;
right: 50%;
transform: translateX(50%);
@ -241,20 +297,27 @@ export const Table: any = observer((props: any) => {
display: none;
}
`,
)}
>
{originNode}
</div>
)}
>
{originNode}
</div>
);
},
...rowSelection,
}
</div>
);
},
...rowSelection,
}
: 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 (
<div
className={css`
@ -264,20 +327,9 @@ export const Table: any = observer((props: any) => {
}
`}
>
<ReactDragListView
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;
`}
>
<SortableWrapper>
<AntdTable
rowKey={defaultRowKey}
rowKey={rowKey ?? defaultRowKey}
{...others}
{...restProps}
pagination={paginationProps}
@ -290,7 +342,7 @@ export const Table: any = observer((props: any) => {
columns={columns}
dataSource={field?.value?.slice?.()}
/>
</ReactDragListView>
</SortableWrapper>
</div>
);
});