mirror of
https://github.com/vrc-get/vrc-get.git
synced 2026-06-21 09:58:08 +00:00
fix: preserve drag position when cursor leaves list bounds
Track last valid sort order via dragOverOrderRef during onDragOver so that when the pointer leaves the sortable region (triggering cancel or landing on a non-user-repo row), the item stays at its last reached position instead of snapping back or jumping to the end of the list. Also disable pointer-events on HNavBar while dragging to prevent Radix UI from stealing pointer capture.
This commit is contained in:
parent
930715960e
commit
45ed34f573
1 changed files with 87 additions and 37 deletions
|
|
@ -4,6 +4,7 @@ import {
|
|||
closestCenter,
|
||||
DndContext,
|
||||
type DragEndEvent,
|
||||
type DragOverEvent,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
|
|
@ -28,6 +29,7 @@ import {
|
|||
useEffect,
|
||||
useId,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { HNavBar, VStack } from "@/components/layout";
|
||||
|
|
@ -131,6 +133,9 @@ function PageBody() {
|
|||
[userRepos],
|
||||
);
|
||||
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragOverOrderRef = useRef<string[] | null>(null);
|
||||
|
||||
const sensors = useSensors(useSensor(PointerSensor));
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
|
@ -142,14 +147,54 @@ function PageBody() {
|
|||
},
|
||||
});
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
function handleDragStart() {
|
||||
dragOverOrderRef.current = null;
|
||||
setIsDragging(true);
|
||||
}
|
||||
|
||||
function handleDragOver(event: DragOverEvent) {
|
||||
const { active, over } = event;
|
||||
if (!over || active.id === over.id) return;
|
||||
const oldIndex = orderedIds.indexOf(active.id as string);
|
||||
const newIndex = orderedIds.indexOf(over.id as string);
|
||||
const newIds = arrayMove(orderedIds, oldIndex, newIndex);
|
||||
setOrderedIds(newIds);
|
||||
reorderMutation.mutate(newIds);
|
||||
if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) {
|
||||
dragOverOrderRef.current = arrayMove(orderedIds, oldIndex, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const pendingOrder = dragOverOrderRef.current;
|
||||
dragOverOrderRef.current = null;
|
||||
setIsDragging(false);
|
||||
const { active, over } = event;
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
const oldIndex = orderedIds.indexOf(active.id as string);
|
||||
const newIndex = orderedIds.indexOf(over.id as string);
|
||||
if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) {
|
||||
const newIds = arrayMove(orderedIds, oldIndex, newIndex);
|
||||
setOrderedIds(newIds);
|
||||
reorderMutation.mutate(newIds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// over が無効(Official/Curated など orderedIds 外の ID)な場合は
|
||||
// onDragOver で追跡していた最後の有効な順序を使う
|
||||
if (pendingOrder) {
|
||||
setOrderedIds(pendingOrder);
|
||||
reorderMutation.mutate(pendingOrder);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragCancel() {
|
||||
setIsDragging(false);
|
||||
const pendingOrder = dragOverOrderRef.current;
|
||||
dragOverOrderRef.current = null;
|
||||
if (pendingOrder) {
|
||||
setOrderedIds(pendingOrder);
|
||||
reorderMutation.mutate(pendingOrder);
|
||||
}
|
||||
}
|
||||
|
||||
const bodyAnimation = usePrevPathName().startsWith("/packages")
|
||||
|
|
@ -160,43 +205,48 @@ function PageBody() {
|
|||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
>
|
||||
<VStack>
|
||||
<HNavBar
|
||||
className="shrink-0"
|
||||
leading={<HeadingPageName pageType={"/packages/repositories"} />}
|
||||
trailing={
|
||||
<DropdownMenu>
|
||||
<div className={"flex divide-x"}>
|
||||
<Button
|
||||
className={"rounded-r-none compact:h-10"}
|
||||
onClick={() => openAddRepositoryDialog()}
|
||||
>
|
||||
{tc("vpm repositories:button:add repository")}
|
||||
</Button>
|
||||
<DropdownMenuTrigger
|
||||
asChild
|
||||
className={"rounded-l-none pl-2 pr-2 compact:h-10"}
|
||||
>
|
||||
<Button>
|
||||
<ChevronDown className={"w-4 h-4"} />
|
||||
<div style={isDragging ? { pointerEvents: "none" } : undefined}>
|
||||
<HNavBar
|
||||
className="shrink-0"
|
||||
leading={<HeadingPageName pageType={"/packages/repositories"} />}
|
||||
trailing={
|
||||
<DropdownMenu>
|
||||
<div className={"flex divide-x"}>
|
||||
<Button
|
||||
className={"rounded-r-none compact:h-10"}
|
||||
onClick={() => openAddRepositoryDialog()}
|
||||
>
|
||||
{tc("vpm repositories:button:add repository")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</div>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
onClick={() => importRepositoriesMutation.mutate()}
|
||||
>
|
||||
{tc("vpm repositories:button:import repositories")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => exportRepositories.mutate()}>
|
||||
{tc("vpm repositories:button:export repositories")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuTrigger
|
||||
asChild
|
||||
className={"rounded-l-none pl-2 pr-2 compact:h-10"}
|
||||
>
|
||||
<Button>
|
||||
<ChevronDown className={"w-4 h-4"} />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</div>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
onClick={() => importRepositoriesMutation.mutate()}
|
||||
>
|
||||
{tc("vpm repositories:button:import repositories")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => exportRepositories.mutate()}>
|
||||
{tc("vpm repositories:button:export repositories")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<main
|
||||
className={`shrink overflow-hidden flex w-full h-full ${bodyAnimation}`}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue