fix: drag providers caused crash (#8906)

* feat(ProviderSettings): 在搜索时禁用列表拖拽功能

当搜索文本不为空时,禁用可拖拽列表的拖拽功能,避免用户在搜索时意外拖动项目

* fix(DraggableList): 修复拖拽列表项在禁用状态下的光标样式

* style(ProviderSettings): 移除列表项的cursor: grab样式

* style(ProviderSettings): 禁止用户选择列表项文本

* fix(ProviderSettings): 为ProviderLogo添加draggable="false"属性防止拖动

* test(DraggableVirtualList): 更新快照测试添加抓取光标样式
This commit is contained in:
Phantom 2025-08-07 13:19:48 +08:00 committed by GitHub
parent ea890c41af
commit 4ce1218d6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 61 additions and 51 deletions

View File

@ -48,6 +48,7 @@ interface DraggableVirtualListProps<T> {
overscan?: number
header?: React.ReactNode
children: (item: T, index: number) => React.ReactNode
disabled?: boolean
}
/**
@ -73,7 +74,8 @@ function DraggableVirtualList<T>({
estimateSize: _estimateSize,
overscan = 5,
header,
children
children,
disabled
}: DraggableVirtualListProps<T>): React.ReactElement {
const _onDragEnd = (result: DropResult, provided: ResponderProvided) => {
onDragEnd?.(result, provided)
@ -157,6 +159,7 @@ function DraggableVirtualList<T>({
itemContainerStyle={itemContainerStyle}
virtualizer={virtualizer}
children={children}
disabled={disabled}
/>
))}
</div>
@ -172,53 +175,59 @@ function DraggableVirtualList<T>({
/**
*
*/
const VirtualRow = memo(({ virtualItem, list, children, itemStyle, itemContainerStyle, virtualizer }: any) => {
const item = list[virtualItem.index]
const draggableId = String(virtualItem.key)
return (
<Draggable
key={`draggable_${draggableId}_${virtualItem.index}`}
draggableId={draggableId}
index={virtualItem.index}>
{(provided) => {
const setDragRefs = (el: HTMLElement | null) => {
provided.innerRef(el)
virtualizer.measureElement(el)
}
const VirtualRow = memo(
({ virtualItem, list, children, itemStyle, itemContainerStyle, virtualizer, disabled }: any) => {
const item = list[virtualItem.index]
const draggableId = String(virtualItem.key)
return (
<Draggable
key={`draggable_${draggableId}_${virtualItem.index}`}
draggableId={draggableId}
isDragDisabled={disabled}
index={virtualItem.index}>
{(provided) => {
const setDragRefs = (el: HTMLElement | null) => {
provided.innerRef(el)
virtualizer.measureElement(el)
}
const dndStyle = provided.draggableProps.style
const virtualizerTransform = `translateY(${virtualItem.start}px)`
const dndStyle = provided.draggableProps.style
const virtualizerTransform = `translateY(${virtualItem.start}px)`
// dnd 的 transform 负责拖拽时的位移和让位动画,
// virtualizer 的 translateY 负责将项定位到虚拟列表的正确位置,
// 它们拼接起来可以同时实现拖拽视觉效果和虚拟化定位。
const combinedTransform = dndStyle?.transform
? `${dndStyle.transform} ${virtualizerTransform}`
: virtualizerTransform
// dnd 的 transform 负责拖拽时的位移和让位动画,
// virtualizer 的 translateY 负责将项定位到虚拟列表的正确位置,
// 它们拼接起来可以同时实现拖拽视觉效果和虚拟化定位。
const combinedTransform = dndStyle?.transform
? `${dndStyle.transform} ${virtualizerTransform}`
: virtualizerTransform
return (
<div
{...provided.draggableProps}
ref={setDragRefs}
className="draggable-item"
data-index={virtualItem.index}
style={{
...itemContainerStyle,
...dndStyle,
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: combinedTransform
}}>
<div {...provided.dragHandleProps} className="draggable-content" style={{ ...itemStyle }}>
{item && children(item, virtualItem.index)}
return (
<div
{...provided.draggableProps}
ref={setDragRefs}
className="draggable-item"
data-index={virtualItem.index}
style={{
...itemContainerStyle,
...dndStyle,
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: combinedTransform
}}>
<div
{...provided.dragHandleProps}
className="draggable-content"
style={{ ...itemStyle, cursor: disabled ? 'pointer' : 'grab' }}>
{item && children(item, virtualItem.index)}
</div>
</div>
</div>
)
}}
</Draggable>
)
})
)
}}
</Draggable>
)
}
)
export default DraggableVirtualList

View File

@ -38,7 +38,7 @@ exports[`DraggableVirtualList > snapshot > should match snapshot with custom sty
>
<div
class="draggable-content"
style="background: blue;"
style="background: blue; cursor: grab;"
>
<div>
Item A
@ -56,7 +56,7 @@ exports[`DraggableVirtualList > snapshot > should match snapshot with custom sty
>
<div
class="draggable-content"
style="background: blue;"
style="background: blue; cursor: grab;"
>
<div>
Item B
@ -74,7 +74,7 @@ exports[`DraggableVirtualList > snapshot > should match snapshot with custom sty
>
<div
class="draggable-content"
style="background: blue;"
style="background: blue; cursor: grab;"
>
<div>
Item C

View File

@ -413,12 +413,12 @@ const ProvidersList: FC = () => {
const getProviderAvatar = (provider: Provider) => {
const logoSrc = getProviderLogo(provider.id)
if (logoSrc) {
return <ProviderLogo shape="circle" src={logoSrc} size={25} />
return <ProviderLogo draggable="false" shape="circle" src={logoSrc} size={25} />
}
const customLogo = providerLogos[provider.id]
if (customLogo) {
return <ProviderLogo shape="square" src={customLogo} size={25} />
return <ProviderLogo draggable="false" shape="square" src={customLogo} size={25} />
}
return (
@ -464,6 +464,7 @@ const ProvidersList: FC = () => {
onDragStart={() => setDragging(true)}
estimateSize={useCallback(() => 40, [])}
overscan={3}
disabled={searchText !== ''}
style={{
height: `calc(100% - 2 * ${BUTTON_WRAPPER_HEIGHT}px)`
}}
@ -525,11 +526,11 @@ const ProviderListItem = styled.div`
align-items: center;
padding: 5px 10px;
width: 100%;
cursor: grab;
border-radius: var(--list-item-border-radius);
font-size: 14px;
transition: all 0.2s ease-in-out;
border: 0.5px solid transparent;
user-select: none;
&:hover {
background: var(--color-background-soft);
}