aab941d89c
* refactor: rename sortable dir * refactor: migrate Sortable to the ui package * feat: add stories for Sortable * refactor: add scroller to the vertical story * refactor: improve hints and width * refactor: simplify item style * fix: lint errors * chore: dependencies * refactor: move hooks * fix: import errors * style: format * style: format
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import type { Key } from 'react'
|
|
import { useCallback, useMemo } from 'react'
|
|
|
|
interface UseDndReorderParams<T> {
|
|
/** 原始的、完整的数据列表 */
|
|
originalList: T[]
|
|
/** 当前在界面上渲染的、可能被过滤的列表 */
|
|
filteredList: T[]
|
|
/** 用于更新原始列表状态的函数 */
|
|
onUpdate: (newList: T[]) => void
|
|
/** 用于从列表项中获取唯一ID的属性名或函数 */
|
|
itemKey: keyof T | ((item: T) => Key)
|
|
}
|
|
|
|
/**
|
|
* 增强拖拽排序能力,处理“过滤后列表”与“原始列表”的索引映射问题。
|
|
*
|
|
* @template T 列表项的类型
|
|
* @param params - { originalList, filteredList, onUpdate, idKey }
|
|
* @returns 返回可以直接传递给 Sortable 的 onSortEnd 回调
|
|
*/
|
|
export function useDndReorder<T>({ originalList, filteredList, onUpdate, itemKey }: UseDndReorderParams<T>) {
|
|
const getId = useCallback(
|
|
(item: T) => (typeof itemKey === 'function' ? itemKey(item) : (item[itemKey] as Key)),
|
|
[itemKey]
|
|
)
|
|
|
|
// 创建从 item ID 到其在 *原始列表* 中索引的映射
|
|
const itemIndexMap = useMemo(() => {
|
|
const map = new Map<Key, number>()
|
|
originalList.forEach((item, index) => {
|
|
map.set(getId(item), index)
|
|
})
|
|
return map
|
|
}, [originalList, getId])
|
|
|
|
// 创建一个函数,将 *过滤后列表* 的视图索引转换为 *原始列表* 的数据索引
|
|
const getItemKey = useCallback(
|
|
(index: number): Key => {
|
|
const item = filteredList[index]
|
|
// 如果找不到item,返回视图索引兜底
|
|
if (!item) return index
|
|
|
|
const originalIndex = itemIndexMap.get(getId(item))
|
|
return originalIndex ?? index
|
|
},
|
|
[filteredList, itemIndexMap, getId]
|
|
)
|
|
|
|
// 创建 onSortEnd 回调,封装了所有重排逻辑
|
|
const onSortEnd = useCallback(
|
|
({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
|
|
// 使用 getItemKey 将视图索引转换为数据索引
|
|
const sourceOriginalIndex = getItemKey(oldIndex) as number
|
|
const destOriginalIndex = getItemKey(newIndex) as number
|
|
|
|
// 如果索引转换失败,不进行任何操作
|
|
if (sourceOriginalIndex === undefined || destOriginalIndex === undefined) {
|
|
return
|
|
}
|
|
|
|
if (sourceOriginalIndex === destOriginalIndex) {
|
|
return
|
|
}
|
|
|
|
// 操作原始列表的副本
|
|
const newList = [...originalList]
|
|
const [movedItem] = newList.splice(sourceOriginalIndex, 1)
|
|
newList.splice(destOriginalIndex, 0, movedItem)
|
|
|
|
// 调用外部更新函数
|
|
onUpdate(newList)
|
|
},
|
|
[getItemKey, originalList, onUpdate]
|
|
)
|
|
|
|
return { onSortEnd, itemKey: getItemKey }
|
|
}
|