mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 23:12:38 +08:00
fix: draggable list id type (#9809)
* refactor(dnd): rename idKey to itemKey for clarity * refactor: key and id type for draggable lists * chore: update yarn lock * fix: type error * refactor: improve getId fallbacks
This commit is contained in:
parent
089477eb1e
commit
925d7e2a25
@ -71,8 +71,9 @@ describe('DraggableList', () => {
|
||||
})
|
||||
|
||||
it('should render nothing when list is empty', () => {
|
||||
const emptyList: Array<{ id: string; name: string }> = []
|
||||
render(
|
||||
<DraggableList list={[]} onUpdate={() => {}}>
|
||||
<DraggableList list={emptyList} onUpdate={() => {}}>
|
||||
{(item) => <div data-testid="item">{item.name}</div>}
|
||||
</DraggableList>
|
||||
)
|
||||
|
||||
@ -33,7 +33,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList: mockOriginalList, // 列表未过滤
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -61,7 +61,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -89,7 +89,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList: mockOriginalList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -110,7 +110,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList: mockOriginalList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -136,7 +136,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
ResponderProvided
|
||||
} from '@hello-pangea/dnd'
|
||||
import { droppableReorder } from '@renderer/utils'
|
||||
import { FC, HTMLAttributes } from 'react'
|
||||
import { HTMLAttributes, Key, useCallback } from 'react'
|
||||
|
||||
interface Props<T> {
|
||||
list: T[]
|
||||
@ -17,23 +17,25 @@ interface Props<T> {
|
||||
listStyle?: React.CSSProperties
|
||||
listProps?: HTMLAttributes<HTMLDivElement>
|
||||
children: (item: T, index: number) => React.ReactNode
|
||||
itemKey?: keyof T | ((item: T) => Key)
|
||||
onUpdate: (list: T[]) => void
|
||||
onDragStart?: OnDragStartResponder
|
||||
onDragEnd?: OnDragEndResponder
|
||||
droppableProps?: Partial<DroppableProps>
|
||||
}
|
||||
|
||||
const DraggableList: FC<Props<any>> = ({
|
||||
function DraggableList<T>({
|
||||
children,
|
||||
list,
|
||||
style,
|
||||
listStyle,
|
||||
listProps,
|
||||
itemKey,
|
||||
droppableProps,
|
||||
onDragStart,
|
||||
onUpdate,
|
||||
onDragEnd
|
||||
}) => {
|
||||
}: Props<T>) {
|
||||
const _onDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||
onDragEnd?.(result, provided)
|
||||
if (result.destination) {
|
||||
@ -46,6 +48,17 @@ const DraggableList: FC<Props<any>> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const getId = useCallback(
|
||||
(item: T) => {
|
||||
if (typeof itemKey === 'function') return itemKey(item)
|
||||
if (itemKey) return item[itemKey] as Key
|
||||
if (typeof item === 'string') return item as Key
|
||||
if (item && typeof item === 'object' && 'id' in item) return item.id as Key
|
||||
return undefined
|
||||
},
|
||||
[itemKey]
|
||||
)
|
||||
|
||||
return (
|
||||
<DragDropContext onDragStart={onDragStart} onDragEnd={_onDragEnd}>
|
||||
<Droppable droppableId="droppable" {...droppableProps}>
|
||||
@ -53,9 +66,9 @@ const DraggableList: FC<Props<any>> = ({
|
||||
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
|
||||
<div {...listProps} className="draggable-list-container">
|
||||
{list.map((item, index) => {
|
||||
const id = item.id || item
|
||||
const draggableId = String(getId(item) ?? index)
|
||||
return (
|
||||
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
||||
<Draggable key={`draggable_${draggableId}`} draggableId={draggableId} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
|
||||
@ -9,7 +9,7 @@ interface UseDraggableReorderParams<T> {
|
||||
/** 用于更新原始列表状态的函数 */
|
||||
onUpdate: (newList: T[]) => void
|
||||
/** 用于从列表项中获取唯一ID的属性名或函数 */
|
||||
idKey: keyof T | ((item: T) => Key)
|
||||
itemKey: keyof T | ((item: T) => Key)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,8 +19,16 @@ interface UseDraggableReorderParams<T> {
|
||||
* @param params - { originalList, filteredList, onUpdate, idKey }
|
||||
* @returns 返回可以直接传递给 DraggableVirtualList 的 props: { onDragEnd, itemKey }
|
||||
*/
|
||||
export function useDraggableReorder<T>({ originalList, filteredList, onUpdate, idKey }: UseDraggableReorderParams<T>) {
|
||||
const getId = useCallback((item: T) => (typeof idKey === 'function' ? idKey(item) : (item[idKey] as Key)), [idKey])
|
||||
export function useDraggableReorder<T>({
|
||||
originalList,
|
||||
filteredList,
|
||||
onUpdate,
|
||||
itemKey
|
||||
}: UseDraggableReorderParams<T>) {
|
||||
const getId = useCallback(
|
||||
(item: T) => (typeof itemKey === 'function' ? itemKey(item) : (item[itemKey] as Key)),
|
||||
[itemKey]
|
||||
)
|
||||
|
||||
// 创建从 item ID 到其在 *原始列表* 中索引的映射
|
||||
const itemIndexMap = useMemo(() => {
|
||||
|
||||
@ -208,7 +208,7 @@ const VirtualRow = memo(
|
||||
const draggableId = String(virtualItem.key)
|
||||
return (
|
||||
<Draggable
|
||||
key={`draggable_${draggableId}_${virtualItem.index}`}
|
||||
key={`draggable_${draggableId}`}
|
||||
draggableId={draggableId}
|
||||
isDragDisabled={disabled}
|
||||
index={virtualItem.index}>
|
||||
|
||||
@ -8,7 +8,7 @@ interface UseDndReorderParams<T> {
|
||||
/** 用于更新原始列表状态的函数 */
|
||||
onUpdate: (newList: T[]) => void
|
||||
/** 用于从列表项中获取唯一ID的属性名或函数 */
|
||||
idKey: keyof T | ((item: T) => Key)
|
||||
itemKey: keyof T | ((item: T) => Key)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,8 +18,11 @@ interface UseDndReorderParams<T> {
|
||||
* @param params - { originalList, filteredList, onUpdate, idKey }
|
||||
* @returns 返回可以直接传递给 Sortable 的 onSortEnd 回调
|
||||
*/
|
||||
export function useDndReorder<T>({ originalList, filteredList, onUpdate, idKey }: UseDndReorderParams<T>) {
|
||||
const getId = useCallback((item: T) => (typeof idKey === 'function' ? idKey(item) : (item[idKey] as Key)), [idKey])
|
||||
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(() => {
|
||||
|
||||
@ -55,7 +55,7 @@ const McpServersList: FC = () => {
|
||||
originalList: mcpServers,
|
||||
filteredList: filteredMcpServers,
|
||||
onUpdate: updateMcpServers,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -321,7 +321,7 @@ const ProviderList: FC = () => {
|
||||
originalList: providers,
|
||||
filteredList: filteredProviders,
|
||||
onUpdate: updateProviders,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user