-
Notifications
You must be signed in to change notification settings - Fork 358
Expand file tree
/
Copy pathuseDragSort.ts
More file actions
230 lines (215 loc) · 9.52 KB
/
useDragSort.ts
File metadata and controls
230 lines (215 loc) · 9.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// 表格 行拖拽 + 列拖拽功能
import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react';
import Sortable, { SortableEvent, SortableOptions, MoveEvent } from 'sortablejs';
import { get } from 'lodash-es';
import log from '@tdesign/common-js/log/index';
import swapDragArrayElement from '@tdesign/common-js/utils/swapDragArrayElement';
import { getColumnDataByKey, getColumnIndexByKey } from '@tdesign/common-js/table/utils';
import { PaginationProps } from '../../pagination';
import { TableRowData, TdPrimaryTableProps, DragSortContext } from '../type';
import useClassName from './useClassName';
import { hasClass } from '../../_util/style';
import useLatest from '../../hooks/useLatest';
import { BaseTableColumns } from '../interface';
export default function useDragSort(
props: TdPrimaryTableProps,
{
primaryTableRef,
innerPagination,
}: {
primaryTableRef: MutableRefObject<any>;
innerPagination: MutableRefObject<PaginationProps>;
},
) {
const { sortOnRowDraggable, dragSort, data, onDragSort } = props;
const { tableDraggableClasses, tableBaseClass, tableFullRowClasses } = useClassName();
const [columns, setDragSortColumns] = useState<BaseTableColumns>(props.columns || []);
// 判断是否有拖拽列。此处重点测试树形结构的拖拽排序
const dragCol = useMemo(() => columns.find((item) => item.colKey === 'drag'), [columns]);
// 行拖拽判断条件
const isRowDraggable = useMemo(() => sortOnRowDraggable || dragSort === 'row', [dragSort, sortOnRowDraggable]);
// 行拖拽判断条件-手柄列
const isRowHandlerDraggable = useMemo(
() => ['row-handler', 'row-handler-col'].includes(dragSort) && !!dragCol,
[dragSort, dragCol],
);
// 列拖拽判断条件
const isColDraggable = useMemo(() => ['col', 'row-handler-col'].includes(dragSort), [dragSort]);
// 为实现受控,存储上一次的变化结果。React 在回调函数中无法获取最新的 state/props 值,因此使用 useRef
const lastRowList = useRef([]);
// React 在回调函数中无法获取最新的 state/props 值,因此使用 useRef
const tData = useRef<TableRowData[]>(null);
const lastColList = useRef([]);
const dragColumns = useRef([]);
const originalColumns = useRef([]);
// 拖拽实例
let dragColInstanceTmp: Sortable = null;
if (props.sortOnRowDraggable) {
log.warn('Table', "`sortOnRowDraggable` is going to be deprecated, use dragSort='row' instead.");
}
useEffect(() => {
// 更新排列顺序
lastRowList.current = data.map((item) => String(get(item, props.rowKey)));
tData.current = data;
}, [data, props.rowKey]);
useEffect(() => {
lastColList.current = props.columns.map((t) => t.colKey);
dragColumns.current = props.columns;
originalColumns.current = props.columns;
}, [props.columns]);
// fix: https://github.com/Tencent/tdesign/issues/294 修正 onDragSort 会使用旧的变量问题
const onDragSortRef = useLatest(onDragSort);
// 本地分页的表格,index 不同,需加上分页计数
function getDataPageIndex(index: number, pagination: PaginationProps) {
const current = pagination.current ?? pagination.defaultCurrent;
const pageSize = pagination.pageSize ?? pagination.defaultPageSize;
// 开启本地分页的场景
if (!props.disableDataPage && pagination && data.length > pageSize) {
return pageSize * (current - 1) + index;
}
return index;
}
const registerRowDragEvent = (element: HTMLElement) => {
/**
* 若table内容未渲染(即element子元素为空)或者 表格无拖动配置,拖拽事件不注册
*/
if (element?.children?.length === 0 || (!isRowHandlerDraggable && !isRowDraggable)) return;
// 拖拽实例
let dragInstanceTmp: Sortable = null;
const dragContainer = element?.querySelector('tbody');
if (!dragContainer) {
console.error('tbody does not exist.');
return null;
}
const baseOptions: SortableOptions = {
animation: 150,
ghostClass: tableDraggableClasses.ghost,
chosenClass: tableDraggableClasses.chosen,
dragClass: tableDraggableClasses.dragging,
filter: `.${tableFullRowClasses.base}`, // 过滤首行尾行固定
onMove: (evt: MoveEvent) => !hasClass(evt.related, tableFullRowClasses.base),
onEnd: (evt: SortableEvent) => {
if (evt.newIndex === evt.oldIndex) return;
// 处理受控:拖拽列表恢复原始排序,等待外部数据 data 变化,更新最终顺序
let { oldIndex: currentIndex, newIndex: targetIndex } = evt;
dragInstanceTmp?.sort([...lastRowList.current]);
if (props.firstFullRow) {
currentIndex -= 1;
targetIndex -= 1;
}
if (innerPagination.current) {
currentIndex = getDataPageIndex(currentIndex, innerPagination.current);
targetIndex = getDataPageIndex(targetIndex, innerPagination.current);
}
const newData = swapDragArrayElement([...tData.current], currentIndex, targetIndex);
const params: DragSortContext<TableRowData> = {
currentIndex,
current: tData.current[currentIndex],
targetIndex,
target: tData.current[targetIndex],
data: tData.current,
newData,
e: evt,
sort: 'row',
};
// currentData is going to be deprecated.
params.currentData = params.newData;
onDragSortRef.current?.(params);
},
...props.dragSortOptions,
};
if (!dragContainer) return;
if (isRowDraggable) {
dragInstanceTmp = new Sortable(dragContainer, { ...baseOptions });
} else if (isRowHandlerDraggable) {
dragInstanceTmp = new Sortable(dragContainer, {
...baseOptions,
handle: `.${tableDraggableClasses.handle}`,
});
}
lastRowList.current = dragInstanceTmp?.toArray();
};
const registerOneLevelColDragEvent = (container: HTMLElement, recover: boolean) => {
const options: SortableOptions = {
animation: 150,
dataIdAttr: 'data-colkey',
direction: 'vertical',
ghostClass: tableDraggableClasses.ghost,
chosenClass: tableDraggableClasses.chosen,
dragClass: tableDraggableClasses.dragging,
handle: `.${tableBaseClass.thCellInner}`,
// 存在类名:t-table__th--drag-sort 的列才允许拖拽调整顺序(注意:添加 draggable 之后,固定列的表头 和 吸顶表头 位置顺序会错位,暂时注释)
// draggable: `th.${tableDraggableClasses.dragSortTh}`,
onEnd: (evt: SortableEvent) => {
if (evt.newIndex === evt.oldIndex) return;
if (recover) {
// 处理受控:拖拽列表恢复原始排序,等待外部数据 data 变化,更新最终顺序
dragColInstanceTmp?.sort([...lastColList.current]);
}
const { oldIndex, newIndex, target: targetElement } = evt;
let currentIndex = recover ? oldIndex : newIndex;
let targetIndex = recover ? newIndex : oldIndex;
const oldElement = targetElement.children[currentIndex] as HTMLElement;
const newElement = targetElement.children[targetIndex] as HTMLElement;
const current = getColumnDataByKey(originalColumns.current, oldElement.dataset.colkey);
const target = getColumnDataByKey(originalColumns.current, newElement.dataset.colkey);
if (!current || !current.colKey) {
log.error('Table', `colKey is missing in ${JSON.stringify(current)}`);
}
if (!target || !target.colKey) {
log.error('Table', `colKey is missing in ${JSON.stringify(target)}`);
}
// 寻找外部数据 props.columns 中的真正下标
currentIndex = getColumnIndexByKey(originalColumns.current, current.colKey);
targetIndex = getColumnIndexByKey(originalColumns.current, target.colKey);
const params: DragSortContext<TableRowData> = {
data: dragColumns.current,
currentIndex,
current,
targetIndex,
target,
newData: swapDragArrayElement([...originalColumns.current], currentIndex, targetIndex),
e: evt,
sort: 'col',
};
// currentData is going to be deprecated
params.currentData = params.newData;
onDragSortRef.current?.(params);
},
...props.dragSortOptions,
};
if (!container) return;
dragColInstanceTmp = new Sortable(container, options);
return dragColInstanceTmp;
};
const registerColDragEvent = (tableElement: HTMLElement) => {
if (!isColDraggable || !tableElement) return;
const trList = tableElement.querySelectorAll('thead > tr');
if (trList.length <= 1) {
const container = trList[0];
const dragInstanceTmp = registerOneLevelColDragEvent(container as HTMLElement, true);
lastColList.current = dragInstanceTmp?.toArray();
} else {
// 多级表头只抛出事件,不处理其他未知逻辑(如多层表头之间具体如何交换)
trList?.forEach((container) => {
registerOneLevelColDragEvent(container as HTMLElement, false);
});
}
};
// 注册拖拽事件
useEffect(() => {
if (!primaryTableRef.current) return;
registerRowDragEvent(primaryTableRef.current.tableElement);
registerColDragEvent(primaryTableRef.current.tableHtmlElement);
primaryTableRef.current.onAffixHeaderMount((el: HTMLDivElement) => {
registerColDragEvent(el);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns, dragSort, innerPagination]);
return {
isRowDraggable,
isRowHandlerDraggable,
isColDraggable,
setDragSortColumns,
};
}