mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
* 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 }
|
||
}
|