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:
one 2025-09-02 23:28:29 +08:00 committed by GitHub
parent 089477eb1e
commit 925d7e2a25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 45 additions and 20 deletions

View File

@ -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>
)

View File

@ -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'
})
)

View File

@ -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}

View File

@ -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(() => {

View File

@ -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}>

View File

@ -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(() => {

View File

@ -55,7 +55,7 @@ const McpServersList: FC = () => {
originalList: mcpServers,
filteredList: filteredMcpServers,
onUpdate: updateMcpServers,
idKey: 'id'
itemKey: 'id'
})
const scrollRef = useRef<HTMLDivElement>(null)

View File

@ -321,7 +321,7 @@ const ProviderList: FC = () => {
originalList: providers,
filteredList: filteredProviders,
onUpdate: updateProviders,
idKey: 'id'
itemKey: 'id'
})
const handleDragStart = useCallback(() => {