fix: prevent snap-back after drag and click-opens-edit during drag
Two fixes: - Remove onSettled clearing tempItems before refetch completes, let useEffect clear it when fresh server data arrives - Track isDragging ref to suppress edit panel click after drag - Remove layout="position" which interfered with reorder detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Reorder } from "framer-motion";
|
import { Reorder } from "framer-motion";
|
||||||
|
import { useRef } from "react";
|
||||||
import { useFormatters } from "../hooks/useFormatters";
|
import { useFormatters } from "../hooks/useFormatters";
|
||||||
import type { CandidateDelta } from "../hooks/useImpactDeltas";
|
import type { CandidateDelta } from "../hooks/useImpactDeltas";
|
||||||
import { LucideIcon } from "../lib/iconData";
|
import { LucideIcon } from "../lib/iconData";
|
||||||
@@ -56,6 +57,7 @@ export function CandidateListItem({
|
|||||||
delta,
|
delta,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
}: CandidateListItemProps) {
|
}: CandidateListItemProps) {
|
||||||
|
const isDragging = useRef(false);
|
||||||
const { weight, price } = useFormatters();
|
const { weight, price } = useFormatters();
|
||||||
const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel);
|
const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel);
|
||||||
const openConfirmDeleteCandidate = useUIStore(
|
const openConfirmDeleteCandidate = useUIStore(
|
||||||
@@ -99,7 +101,10 @@ export function CandidateListItem({
|
|||||||
{/* Name + badges */}
|
{/* Name + badges */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => openCandidateEditPanel(candidate.id)}
|
onClick={() => {
|
||||||
|
if (isDragging.current) return;
|
||||||
|
openCandidateEditPanel(candidate.id);
|
||||||
|
}}
|
||||||
className="flex-1 min-w-0 text-left"
|
className="flex-1 min-w-0 text-left"
|
||||||
>
|
>
|
||||||
<p className="text-sm font-semibold text-gray-900 truncate">
|
<p className="text-sm font-semibold text-gray-900 truncate">
|
||||||
@@ -211,14 +216,21 @@ export function CandidateListItem({
|
|||||||
return (
|
return (
|
||||||
<Reorder.Item
|
<Reorder.Item
|
||||||
value={candidate}
|
value={candidate}
|
||||||
onDragEnd={onDragEnd}
|
onDragStart={() => {
|
||||||
layout="position"
|
isDragging.current = true;
|
||||||
|
}}
|
||||||
|
onDragEnd={() => {
|
||||||
|
// Delay clearing so onClick can check it
|
||||||
|
setTimeout(() => {
|
||||||
|
isDragging.current = false;
|
||||||
|
}, 0);
|
||||||
|
onDragEnd?.();
|
||||||
|
}}
|
||||||
whileDrag={{
|
whileDrag={{
|
||||||
scale: 1.02,
|
scale: 1.02,
|
||||||
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
|
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
|
||||||
cursor: "grabbing",
|
cursor: "grabbing",
|
||||||
}}
|
}}
|
||||||
transition={{ layout: { duration: 0.15, ease: "easeOut" } }}
|
|
||||||
style={{ marginBottom: 8, cursor: "grab" }}
|
style={{ marginBottom: 8, cursor: "grab" }}
|
||||||
className={sharedClassName}
|
className={sharedClassName}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -87,10 +87,9 @@ function ThreadDetailPage() {
|
|||||||
|
|
||||||
function handleDragEnd() {
|
function handleDragEnd() {
|
||||||
if (!tempItems) return;
|
if (!tempItems) return;
|
||||||
reorderMutation.mutate(
|
reorderMutation.mutate({
|
||||||
{ orderedIds: tempItems.map((c) => c.id) },
|
orderedIds: tempItems.map((c) => c.id),
|
||||||
{ onSettled: () => setTempItems(null) },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user